From 45a7bf6cc1d8ac2db41bdde776d5e105d1bd0a70 Mon Sep 17 00:00:00 2001 From: NikolayRn Date: Thu, 6 Nov 2025 14:36:33 +0200 Subject: [PATCH 1/7] smart-wallets-legacy add error handling --- .../services/smart-wallets-legacy.service.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 c2afdc0e..d5ffcab2 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 @@ -161,12 +161,24 @@ export class SmartWalletsLegacyService implements SmartWalletService { async relay (relayDto: RelayDto) { try { const transactionId = generateTransactionId(relayDto.data) - await this.centClient.subscribe({ channel: `transaction:#${transactionId}`, user: relayDto.ownerAddress }) + + // Non-blocking subscription with error handling + this.centClient.subscribe({ + channel: `transaction:#${transactionId}`, + user: relayDto.ownerAddress + }).catch(err => { + this.logger.error(`Centrifugo subscription failed: ${err}`) + }) + + // Fire-and-forget relay call with error handling this.relayAPIService.relay({ v2: true, transactionId, ...relayDto + }).catch(err => { + this.logger.error(`Relay API call failed: ${err}`) }) + return { connectionUrl: this.wsUrl, transactionId From bab1c759710e8517ae54adbb504fa1d2dc304228 Mon Sep 17 00:00:00 2001 From: NikolayRn Date: Thu, 6 Nov 2025 15:53:09 +0200 Subject: [PATCH 2/7] prod legacy wallet services --- helm/values/qa.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/values/qa.yaml b/helm/values/qa.yaml index a953c158..8c9e7e4f 100644 --- a/helm/values/qa.yaml +++ b/helm/values/qa.yaml @@ -276,7 +276,7 @@ api-service: 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://staging-wallet.fuse.io" + LEGACY_FUSE_WALLET_API_URL: "https://wallet.fuse.io" VOLTAGE_ROUTER_API_URL: "https://router.voltage.finance" nodeSelector: {} @@ -648,7 +648,7 @@ smart-wallets-service: ACCOUNTS_TCP_PORT: "8875" CENTRIFUGO_URI: "wss://ws.qa.fuse.io/connection/websocket" CENTRIFUGO_API_URL: "https://ws.qa.fuse.io/api" - LEGACY_FUSE_WALLET_API_URL: "https://staging-wallet.fuse.io" + LEGACY_FUSE_WALLET_API_URL: "https://wallet.fuse.io" CHARGE_BASE_URL: "https://api.qa.fuse.io" COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" From b8f598e3e4cce670985e62325900b9906a50a018 Mon Sep 17 00:00:00 2001 From: NikolayRn Date: Thu, 6 Nov 2025 16:31:51 +0200 Subject: [PATCH 3/7] prod centrifugo for legacy wallets --- helm/values/qa.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/values/qa.yaml b/helm/values/qa.yaml index 8c9e7e4f..e6d79431 100644 --- a/helm/values/qa.yaml +++ b/helm/values/qa.yaml @@ -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.qa.fuse.io/connection/websocket" - CENTRIFUGO_API_URL: "https://ws.qa.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.qa.fuse.io" COIN_GECKO_URL: "https://pro-api.coingecko.com/api/v3" From b6d3411a54e12ee597133a746be4a8777fa699f0 Mon Sep 17 00:00:00 2001 From: NikolayRn Date: Mon, 10 Nov 2025 16:20:10 +0200 Subject: [PATCH 4/7] fix/smart_wallet_relay --- .../smart-wallets-legacy.service.spec.ts | 711 ++++++++++++++++++ .../services/smart-wallets-legacy.service.ts | 37 +- package.json | 1 + 3 files changed, 737 insertions(+), 12 deletions(-) create mode 100644 apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.spec.ts diff --git a/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.spec.ts b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.spec.ts new file mode 100644 index 00000000..2c63667a --- /dev/null +++ b/apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.spec.ts @@ -0,0 +1,711 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { SmartWalletsLegacyService } from './smart-wallets-legacy.service' +import { JwtService } from '@nestjs/jwt' +import { ConfigService } from '@nestjs/config' +import RelayAPIService from '@app/smart-wallets-service/common/services/relay-api.service' +import { CentClient } from 'cent.js' +import { smartWalletString } from '@app/smart-wallets-service/smart-wallets/smart-wallets.constants' +import { RpcException } from '@nestjs/microservices' +import { HttpStatus } from '@nestjs/common' +import * as helperFunctions from '@app/smart-wallets-service/common/utils/helper-functions' + +describe('SmartWalletsLegacyService - relay', () => { + let service: SmartWalletsLegacyService + let centClient: jest.Mocked + let relayAPIService: jest.Mocked + let configService: jest.Mocked + + const mockRelayDto: any = { + ownerAddress: '0x1234567890123456789012345678901234567890', + walletAddress: '0x0987654321098765432109876543210987654321', + data: '0xabcdef', + nonce: '1', + methodName: 'execute', + signature: '0x1234', + walletModule: '0xmoduleaddress' + } + + const mockTransactionId = 'test-transaction-id-123' + const mockWsUrl = 'wss://ws.test.io/connection/websocket' + + beforeEach(async () => { + centClient = { + subscribe: jest.fn().mockResolvedValue(undefined), + unsubscribe: jest.fn().mockResolvedValue(undefined), + publish: jest.fn().mockResolvedValue(undefined) + } as any + + relayAPIService = { + relay: jest.fn().mockResolvedValue(undefined), + createWallet: jest.fn().mockResolvedValue(undefined) + } as any + + configService = { + get: jest.fn((key: string) => { + if (key === 'wsUrl') return mockWsUrl + if (key === 'sharedAddresses') return {} + return null + }) + } as any + + const mockModel = { + findOne: jest.fn(), + create: jest.fn(), + findOneAndUpdate: jest.fn() + } + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SmartWalletsLegacyService, + { + provide: JwtService, + useValue: { sign: jest.fn() } + }, + { + provide: ConfigService, + useValue: configService + }, + { + provide: RelayAPIService, + useValue: relayAPIService + }, + { + provide: CentClient, + useValue: centClient + }, + { + provide: smartWalletString, + useValue: mockModel + } + ] + }).compile() + + service = module.get(SmartWalletsLegacyService) + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(mockTransactionId) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('Happy Path', () => { + it('should successfully relay transaction with immediate response', async () => { + const result = await service.relay(mockRelayDto) + + expect(result).toEqual({ + connectionUrl: mockWsUrl, + transactionId: mockTransactionId + }) + expect(helperFunctions.generateTransactionId).toHaveBeenCalledWith(mockRelayDto.data) + }) + + it('should call centClient.subscribe with correct parameters', async () => { + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(centClient.subscribe).toHaveBeenCalledWith({ + channel: `transaction:#${mockTransactionId}`, + user: mockRelayDto.ownerAddress + }) + }) + + it('should call relayAPIService.relay with correct parameters', async () => { + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(relayAPIService.relay).toHaveBeenCalledWith({ + v2: true, + transactionId: mockTransactionId, + ...mockRelayDto + }) + }) + + it('should return immediately without waiting for centClient or relayAPI', async () => { + centClient.subscribe.mockImplementation(() => + new Promise(resolve => setTimeout(resolve, 1000)) + ) + relayAPIService.relay.mockImplementation(() => + new Promise(resolve => setTimeout(resolve, 1000)) + ) + + const startTime = Date.now() + const result = await service.relay(mockRelayDto) + const duration = Date.now() - startTime + + expect(duration).toBeLessThan(100) + expect(result.transactionId).toBe(mockTransactionId) + }) + }) + + describe('Resilience - Centrifugo Failures', () => { + it('should still return success when centClient.subscribe fails', async () => { + centClient.subscribe.mockRejectedValue(new Error('Centrifugo is down')) + + const result = await service.relay(mockRelayDto) + + expect(result).toEqual({ + connectionUrl: mockWsUrl, + transactionId: mockTransactionId + }) + }) + + it('should log error when centClient.subscribe fails', async () => { + const loggerErrorSpy = jest.spyOn(service['logger'], 'error') + centClient.subscribe.mockRejectedValue(new Error('Connection timeout')) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(loggerErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('Centrifugo subscription failed') + ) + }) + + it('should throw RpcException when centClient throws synchronous error', async () => { + centClient.subscribe.mockImplementation(() => { + throw new Error('Immediate failure') + }) + + await expect(service.relay(mockRelayDto)).rejects.toThrow(RpcException) + }) + }) + + describe('Resilience - Relay API Failures', () => { + it('should still return success when relayAPIService.relay fails', async () => { + relayAPIService.relay.mockRejectedValue(new Error('Relay API is down')) + + const result = await service.relay(mockRelayDto) + + expect(result).toEqual({ + connectionUrl: mockWsUrl, + transactionId: mockTransactionId + }) + }) + + it('should log error when relayAPIService.relay fails', async () => { + const loggerErrorSpy = jest.spyOn(service['logger'], 'error') + relayAPIService.relay.mockRejectedValue(new Error('500 Internal Server Error')) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(loggerErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('Relay API call failed') + ) + }) + + it('should handle 5xx errors from relay API gracefully', async () => { + relayAPIService.relay.mockRejectedValue({ + response: { status: 503, data: 'Service Unavailable' } + }) + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBe(mockTransactionId) + }) + }) + + describe('Resilience - Multiple Failures', () => { + it('should still return success when both centClient and relayAPI fail', async () => { + centClient.subscribe.mockRejectedValue(new Error('Centrifugo down')) + relayAPIService.relay.mockRejectedValue(new Error('Relay API down')) + + const result = await service.relay(mockRelayDto) + + expect(result).toEqual({ + connectionUrl: mockWsUrl, + transactionId: mockTransactionId + }) + }) + + it('should log both errors when both services fail', async () => { + const loggerErrorSpy = jest.spyOn(service['logger'], 'error') + centClient.subscribe.mockRejectedValue(new Error('Centrifugo error')) + relayAPIService.relay.mockRejectedValue(new Error('Relay error')) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(loggerErrorSpy).toHaveBeenCalledTimes(2) + expect(loggerErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('Centrifugo subscription failed') + ) + expect(loggerErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('Relay API call failed') + ) + }) + }) + + describe('Error Handling - Critical Failures', () => { + it('should throw RpcException when generateTransactionId fails', async () => { + jest.spyOn(helperFunctions, 'generateTransactionId').mockImplementation(() => { + throw new Error('Invalid data format') + }) + + await expect(service.relay(mockRelayDto)).rejects.toThrow(RpcException) + }) + + it('should throw RpcException with correct status and message', async () => { + jest.spyOn(helperFunctions, 'generateTransactionId').mockImplementation(() => { + throw new Error('Transaction ID generation failed') + }) + + try { + await service.relay(mockRelayDto) + fail('Should have thrown RpcException') + } catch (error) { + expect(error).toBeInstanceOf(RpcException) + expect(error.getError()).toEqual({ + error: 'Transaction ID generation failed', + status: HttpStatus.BAD_REQUEST + }) + } + }) + + it('should log error when critical failure occurs', async () => { + const loggerErrorSpy = jest.spyOn(service['logger'], 'error') + jest.spyOn(helperFunctions, 'generateTransactionId').mockImplementation(() => { + throw new Error('Critical error') + }) + + try { + await service.relay(mockRelayDto) + } catch (error) { + // Expected + } + + expect(loggerErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('An error occurred during relay execution') + ) + }) + }) + + describe('Transaction ID Generation', () => { + it('should generate transaction ID from relay data', async () => { + await service.relay(mockRelayDto) + + expect(helperFunctions.generateTransactionId).toHaveBeenCalledWith(mockRelayDto.data) + }) + + it('should use generated transaction ID in centClient subscription', async () => { + const customTransactionId = 'custom-tx-id-456' + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(customTransactionId) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(centClient.subscribe).toHaveBeenCalledWith({ + channel: `transaction:#${customTransactionId}`, + user: mockRelayDto.ownerAddress + }) + }) + + it('should use generated transaction ID in relay API call', async () => { + const customTransactionId = 'custom-tx-id-789' + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(customTransactionId) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(relayAPIService.relay).toHaveBeenCalledWith( + expect.objectContaining({ + transactionId: customTransactionId + }) + ) + }) + + it('should return generated transaction ID to client', async () => { + const customTransactionId = 'custom-tx-id-abc' + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(customTransactionId) + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBe(customTransactionId) + }) + }) + + describe('Connection URL', () => { + it('should return correct WebSocket URL from config', async () => { + const result = await service.relay(mockRelayDto) + + expect(result.connectionUrl).toBe(mockWsUrl) + expect(configService.get).toHaveBeenCalledWith('wsUrl') + }) + + it('should handle different WebSocket URLs', async () => { + const customWsUrl = 'wss://custom.fuse.io/ws' + configService.get.mockReturnValue(customWsUrl) + + const result = await service.relay(mockRelayDto) + + expect(result.connectionUrl).toBe(customWsUrl) + }) + }) + + describe('Fire-and-Forget Behavior', () => { + it('should not block on slow centClient operations', async () => { + let subscribeResolved = false + centClient.subscribe.mockImplementation(() => + new Promise(resolve => { + setTimeout(() => { + subscribeResolved = true + resolve(undefined) + }, 2000) + }) + ) + + const result = await service.relay(mockRelayDto) + + expect(result).toBeDefined() + expect(subscribeResolved).toBe(false) + }) + + it('should not block on slow relay API operations', async () => { + let relayResolved = false + relayAPIService.relay.mockImplementation(() => + new Promise(resolve => { + setTimeout(() => { + relayResolved = true + resolve(undefined) + }, 2000) + }) + ) + + const result = await service.relay(mockRelayDto) + + expect(result).toBeDefined() + expect(relayResolved).toBe(false) + }) + }) + + describe('Data Integrity', () => { + it('should pass all relay DTO fields to relay API', async () => { + const fullRelayDto = { + ...mockRelayDto, + gasPrice: 20000000000, + gasLimit: 21000 + } + + await service.relay(fullRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(relayAPIService.relay).toHaveBeenCalledWith( + expect.objectContaining({ + ownerAddress: fullRelayDto.ownerAddress, + data: fullRelayDto.data, + walletAddress: fullRelayDto.walletAddress, + nonce: fullRelayDto.nonce, + gasPrice: fullRelayDto.gasPrice, + gasLimit: fullRelayDto.gasLimit, + methodName: fullRelayDto.methodName, + signature: fullRelayDto.signature, + walletModule: fullRelayDto.walletModule + }) + ) + }) + + it('should include v2 flag in relay API call', async () => { + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(relayAPIService.relay).toHaveBeenCalledWith( + expect.objectContaining({ + v2: true + }) + ) + }) + }) + + describe('Production - Config Validation', () => { + it('should throw error when wsUrl is null', async () => { + configService.get.mockReturnValue(null) + + await expect(service.relay(mockRelayDto)).rejects.toThrow() + }) + + it('should throw error when wsUrl is undefined', async () => { + configService.get.mockReturnValue(undefined) + + await expect(service.relay(mockRelayDto)).rejects.toThrow() + }) + + it('should throw error when wsUrl is empty string', async () => { + configService.get.mockReturnValue('') + + await expect(service.relay(mockRelayDto)).rejects.toThrow(RpcException) + }) + }) + + describe('Production - Invalid Input', () => { + it('should handle null relayDto.data gracefully', async () => { + const invalidDto = { ...mockRelayDto, data: null } + + const result = await service.relay(invalidDto) + + expect(result).toBeDefined() + expect(result.connectionUrl).toBe(mockWsUrl) + }) + + it('should handle undefined relayDto.ownerAddress', async () => { + const invalidDto = { ...mockRelayDto, ownerAddress: undefined } + + const result = await service.relay(invalidDto) + + expect(result).toBeDefined() + }) + + it('should handle empty string data', async () => { + const invalidDto = { ...mockRelayDto, data: '' } + + const result = await service.relay(invalidDto) + + expect(result.transactionId).toBe(mockTransactionId) + }) + + it('should handle very large data payload', async () => { + const largeData = '0x' + 'a'.repeat(100000) + const largeDto = { ...mockRelayDto, data: largeData } + + const result = await service.relay(largeDto) + + expect(result.transactionId).toBe(mockTransactionId) + }) + }) + + describe('Production - Transaction ID Edge Cases', () => { + it('should handle null transaction ID', async () => { + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(null as any) + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBeNull() + }) + + it('should handle empty string transaction ID', async () => { + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue('') + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBe('') + expect(result.connectionUrl).toBe(mockWsUrl) + }) + + it('should handle special characters in transaction ID', async () => { + const specialTxId = 'tx:@#$%^&*()123' + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(specialTxId) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(centClient.subscribe).toHaveBeenCalledWith({ + channel: `transaction:#${specialTxId}`, + user: mockRelayDto.ownerAddress + }) + }) + + it('should handle very long transaction ID', async () => { + const longTxId = 'a'.repeat(1000) + jest.spyOn(helperFunctions, 'generateTransactionId').mockReturnValue(longTxId) + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBe(longTxId) + }) + }) + + describe('Production - Promise Behavior', () => { + it('should handle centClient not returning a promise', async () => { + centClient.subscribe = jest.fn() as any + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBe(mockTransactionId) + }) + + it('should handle relayAPI not returning a promise', async () => { + relayAPIService.relay = jest.fn() as any + + const result = await service.relay(mockRelayDto) + + expect(result.transactionId).toBe(mockTransactionId) + }) + + it('should handle promises that never resolve', async () => { + centClient.subscribe.mockImplementation(() => + new Promise(() => { }) // Never resolves + ) + + const startTime = Date.now() + const result = await service.relay(mockRelayDto) + const duration = Date.now() - startTime + + expect(duration).toBeLessThan(100) + expect(result.transactionId).toBe(mockTransactionId) + }) + + it('should handle error without message property', async () => { + const loggerErrorSpy = jest.spyOn(service['logger'], 'error') + centClient.subscribe.mockRejectedValue({ code: 500 } as any) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(loggerErrorSpy).toHaveBeenCalled() + }) + + it('should handle circular reference in error', async () => { + const loggerErrorSpy = jest.spyOn(service['logger'], 'error') + const circularError: any = { message: 'Circular' } + circularError.self = circularError + + centClient.subscribe.mockRejectedValue(circularError) + + await service.relay(mockRelayDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(loggerErrorSpy).toHaveBeenCalled() + }) + }) + + describe('Production - Concurrent Calls', () => { + it('should handle multiple concurrent relay calls', async () => { + const promises = Array.from({ length: 10 }, (_, i) => + service.relay({ + ...mockRelayDto, + data: `0xdata${i}` + }) + ) + + const results = await Promise.all(promises) + + expect(results).toHaveLength(10) + results.forEach(result => { + expect(result.transactionId).toBe(mockTransactionId) + expect(result.connectionUrl).toBe(mockWsUrl) + }) + }) + + it('should handle concurrent calls with mixed success/failure', async () => { + let callCount = 0 + centClient.subscribe.mockImplementation(() => { + callCount++ + if (callCount % 2 === 0) { + return Promise.reject(new Error('Even call failed')) + } + return Promise.resolve(undefined) + }) + + const promises = Array.from({ length: 10 }, () => + service.relay(mockRelayDto) + ) + + const results = await Promise.all(promises) + + expect(results).toHaveLength(10) + results.forEach(result => { + expect(result.transactionId).toBe(mockTransactionId) + }) + }) + }) + + describe('Production - Memory Leak Prevention', () => { + it('should not leak memory with many failed promises', async () => { + centClient.subscribe.mockRejectedValue(new Error('Always fails')) + relayAPIService.relay.mockRejectedValue(new Error('Always fails')) + + const promises = Array.from({ length: 100 }, () => + service.relay(mockRelayDto) + ) + + const results = await Promise.all(promises) + + expect(results).toHaveLength(100) + }) + + it('should handle rapid fire-and-forget without blocking', async () => { + const startTime = Date.now() + + const promises = Array.from({ length: 50 }, () => + service.relay(mockRelayDto) + ) + + await Promise.all(promises) + + const duration = Date.now() - startTime + + expect(duration).toBeLessThan(500) + }) + }) + + describe('Production - Error Message Edge Cases', () => { + it('should handle error with undefined message', async () => { + jest.spyOn(helperFunctions, 'generateTransactionId').mockImplementation(() => { + const err: any = new Error() + delete err.message + throw err + }) + + try { + await service.relay(mockRelayDto) + fail('Should have thrown') + } catch (error) { + expect(error).toBeInstanceOf(RpcException) + expect(error.getError().error).toBe('Relay execution error') + } + }) + }) + + describe('Production - Service Integration', () => { + it('should pass exact relay DTO to relay API without modification', async () => { + const complexDto = { + ...mockRelayDto, + customField: 'should be passed', + nestedObject: { a: 1, b: 2 } + } + + await service.relay(complexDto) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(relayAPIService.relay).toHaveBeenCalledWith( + expect.objectContaining({ + v2: true, + transactionId: mockTransactionId, + customField: 'should be passed', + nestedObject: { a: 1, b: 2 } + }) + ) + }) + + it('should override v2 flag even if relayDto has v2: false', async () => { + const dtoWithV2False = { ...mockRelayDto, v2: false } + + await service.relay(dtoWithV2False) + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(relayAPIService.relay).toHaveBeenCalledWith( + expect.objectContaining({ + v2: true + }) + ) + }) + }) + + describe('Production - State Consistency', () => { + it('should maintain consistent state across multiple calls', async () => { + const call1 = await service.relay(mockRelayDto) + const call2 = await service.relay(mockRelayDto) + const call3 = await service.relay(mockRelayDto) + + expect(call1.connectionUrl).toBe(call2.connectionUrl) + expect(call2.connectionUrl).toBe(call3.connectionUrl) + }) + + it('should not mutate input relayDto', async () => { + const dtoSnapshot = JSON.parse(JSON.stringify(mockRelayDto)) + + await service.relay(mockRelayDto) + + expect(mockRelayDto).toEqual(dtoSnapshot) + }) + }) +}) 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 d5ffcab2..026a158c 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 @@ -7,8 +7,8 @@ import { arrayify, computeAddress, hashMessage, recoverPublicKey } from 'nestjs- import { ConfigService } from '@nestjs/config' import { smartWalletString } from '@app/smart-wallets-service/smart-wallets/smart-wallets.constants' import { SmartWallet, SmartWalletService } from '@app/smart-wallets-service/smart-wallets/interfaces/smart-wallets.interface' -import { generateSalt, generateTransactionId } from 'apps/charge-smart-wallets-service/src/common/utils/helper-functions' -import RelayAPIService from 'apps/charge-smart-wallets-service/src/common/services/relay-api.service' +import { generateSalt, generateTransactionId } from '@app/smart-wallets-service/common/utils/helper-functions' +import RelayAPIService from '@app/smart-wallets-service/common/services/relay-api.service' import { RelayDto } from '@app/smart-wallets-service/smart-wallets/dto/relay.dto' import { ISmartWalletUser } from '@app/common/interfaces/smart-wallet.interface' import { CentClient } from 'cent.js' @@ -161,26 +161,39 @@ export class SmartWalletsLegacyService implements SmartWalletService { async relay (relayDto: RelayDto) { try { const transactionId = generateTransactionId(relayDto.data) + const wsUrl = this.wsUrl + + if (!wsUrl) { + throw new Error('WebSocket URL is not configured') + } // Non-blocking subscription with error handling - this.centClient.subscribe({ + const subscribePromise = this.centClient.subscribe({ channel: `transaction:#${transactionId}`, user: relayDto.ownerAddress - }).catch(err => { - this.logger.error(`Centrifugo subscription failed: ${err}`) }) - // Fire-and-forget relay call with error handling - this.relayAPIService.relay({ - v2: true, + if (subscribePromise && typeof subscribePromise.catch === 'function') { + subscribePromise.catch(err => { + this.logger.error(`Centrifugo subscription failed: ${err}`) + }) + } + + // Fire-and-forget relay call with error handling (v2 flag MUST be set AFTER spread) + const relayPromise = this.relayAPIService.relay({ + ...relayDto, transactionId, - ...relayDto - }).catch(err => { - this.logger.error(`Relay API call failed: ${err}`) + v2: true }) + if (relayPromise && typeof relayPromise.catch === 'function') { + relayPromise.catch(err => { + this.logger.error(`Relay API call failed: ${err}`) + }) + } + return { - connectionUrl: this.wsUrl, + connectionUrl: wsUrl, transactionId } } catch (err) { diff --git a/package.json b/package.json index 063ec44e..2f4bbadd 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./apps/charge-accounts-service/test/jest-e2e.json", + "sw-test": "jest apps/charge-smart-wallets-service/src/smart-wallets/services/smart-wallets-legacy.service.spec.ts --config ./jest.config.ts", "documentation:serve": "npx @compodoc/compodoc -p tsconfig.json --serve", "docker:staging": "docker-compose -f docker-compose.yml -f docker-compose.staging.yml up -d", "docker:dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up", From be300f16b9caef6f10cd7db0a5938399b21f764b Mon Sep 17 00:00:00 2001 From: Lior Agnin Date: Tue, 18 Nov 2025 11:38:33 +0200 Subject: [PATCH 5/7] Update API documentation with latest changes Regenerate documentation to reflect recent code changes including new DTOs (CreateChargeBridgeDto, CreateOperatorCheckoutDto, MultipleTokenPricesDto), services (LiquidStakingFuseClient, SimpleStakingService, VoltBarClient), and updated error handling in AllExceptionsFilter. --- .../classes/AllExceptionsFilter.html | 28 +- documentation/classes/BatchTransaction.html | 17 +- .../classes/CreateChargeBridgeDto.html | 269 ++ .../classes/CreateOperatorCheckoutDto.html | 310 ++ .../classes/MultipleTokenPricesDto.html | 228 + documentation/classes/ScannerService.html | 2 +- .../controllers/ApiKeysController-1.html | 360 +- .../controllers/ApiKeysController.html | 360 +- .../controllers/BalancesAPIController.html | 12 +- .../controllers/DataLayerController.html | 95 +- .../controllers/OperatorsController.html | 1253 +++++- .../controllers/StakingApiV2Controller.html | 491 +++ .../controllers/StakingController.html | 318 +- .../controllers/TradeApiController.html | 137 +- .../controllers/VoltageDexController.html | 105 +- documentation/coverage.html | 492 ++- documentation/dependencies.html | 2 + documentation/graph/dependencies.svg | 1718 ++++---- documentation/guards/CronGuard.html | 264 ++ .../guards/IsValidApiKeysGuard-1.html | 24 +- documentation/guards/IsValidApiKeysGuard.html | 24 +- .../images/coverage-badge-documentation.svg | 2 +- .../injectables/ApiKeysService-1.html | 466 +- documentation/injectables/ApiKeysService.html | 466 +- .../injectables/BroadcasterService.html | 315 +- .../injectables/BundlerApiInterceptor.html | 222 +- .../injectables/ConsensusService.html | 208 +- .../injectables/DataLayerService.html | 45 +- .../ERC20EventsScannerService.html | 121 +- .../injectables/EventsScannerService.html | 6 +- .../injectables/ExplorerService.html | 67 +- documentation/injectables/GraphService.html | 113 +- .../injectables/LiquidStakingFuseClient.html | 290 ++ .../injectables/OperatorJwtStrategy.html | 92 +- .../injectables/OperatorsService.html | 3753 +++++++++++++++-- .../injectables/PaymasterApiService.html | 42 +- .../injectables/RelayAPIService.html | 89 +- .../injectables/SimpleStakingService.html | 1092 +++++ .../injectables/SmartWalletsAAService.html | 29 +- .../SmartWalletsLegacyService.html | 195 +- .../injectables/StakingAPIService.html | 296 +- documentation/injectables/StakingService.html | 524 ++- .../injectables/TokenAddressMapper.html | 150 +- .../injectables/TokenPriceService.html | 426 +- .../injectables/TokenStatsService.html | 135 +- .../injectables/TradeApiService.html | 95 +- .../TransactionsScannerService.html | 163 +- .../injectables/UnmarshalService.html | 702 ++- .../UserOpEventsScannerService.html | 10 +- documentation/injectables/VoltBarClient.html | 289 ++ .../injectables/VoltageDexService.html | 100 +- .../injectables/VoltageV2Client.html | 201 +- .../injectables/VoltageV3Client.html | 108 +- .../injectables/WebhooksService.html | 4 +- documentation/interfaces/ApiKey-1.html | 110 +- documentation/interfaces/ApiKey.html | 110 +- documentation/interfaces/BalanceService.html | 44 + documentation/interfaces/CacheOptions.html | 10 +- documentation/interfaces/ChargeBridge.html | 611 +++ .../ChargeCheckoutWebhookEvent.html | 291 ++ .../ExplorerServiceCollectibleResponse.html | 496 +++ .../ExplorerServiceGraphQLVariables.html | 418 ++ ...ExplorerServiceTransformedCollectible.html | 607 +++ documentation/interfaces/GasDetails.html | 14 +- documentation/interfaces/NFTMetadata.html | 647 +++ .../interfaces/OperatorCheckout.html | 699 +++ documentation/interfaces/OperatorInvoice.html | 379 ++ documentation/interfaces/OperatorProject.html | 577 +++ .../interfaces/OperatorRefreshToken.html | 339 ++ documentation/interfaces/OperatorUser.html | 497 +++ .../OperatorUserProjectResponse.html | 254 ++ documentation/interfaces/OperatorWallet.html | 46 + documentation/interfaces/StakedToken.html | 1 + documentation/interfaces/StakingOption.html | 46 + documentation/interfaces/StakingProvider.html | 1 + documentation/interfaces/Token.html | 1 + documentation/interfaces/User.html | 40 + .../interfaces/UserStakedTokens.html | 1 + documentation/js/menu-wc.js | 148 +- documentation/js/search/search_index.js | 4 +- documentation/miscellaneous/enumerations.html | 158 + documentation/miscellaneous/functions.html | 101 +- documentation/miscellaneous/typealiases.html | 61 + documentation/miscellaneous/variables.html | 1054 +++-- documentation/modules/BalancesAPIModule.html | 35 +- .../BalancesAPIModule/dependencies.svg | 30 +- documentation/modules/BalancesModule.html | 7 +- documentation/modules/BroadcasterModule.html | 8 +- .../BroadcasterModule/dependencies.svg | 8 +- documentation/modules/BundlerApiModule.html | 37 +- .../modules/BundlerApiModule/dependencies.svg | 30 +- documentation/modules/ChargeApiModule.html | 40 +- .../modules/ChargeApiModule/dependencies.svg | 40 +- documentation/modules/ConsensusModule.html | 8 +- .../modules/ConsensusModule/dependencies.svg | 8 +- .../modules/EventsScannerModule.html | 77 +- .../EventsScannerModule/dependencies.svg | 38 +- documentation/modules/GraphqlAPIModule.html | 30 +- .../modules/GraphqlAPIModule/dependencies.svg | 30 +- .../modules/NotificationsModule.html | 40 +- .../NotificationsModule/dependencies.svg | 40 +- documentation/modules/OperatorsModule.html | 169 +- .../modules/OperatorsModule/dependencies.svg | 160 +- documentation/modules/PaymasterModule.html | 58 +- .../modules/PaymasterModule/dependencies.svg | 58 +- documentation/modules/ProjectsModule.html | 8 +- .../modules/ProjectsModule/dependencies.svg | 8 +- .../modules/RelayAccountsModule.html | 8 +- .../RelayAccountsModule/dependencies.svg | 8 +- .../modules/SmartWalletsAPIModule.html | 38 +- .../SmartWalletsAPIModule/dependencies.svg | 38 +- documentation/modules/SmartWalletsModule.html | 110 +- .../SmartWalletsModule/dependencies.svg | 110 +- documentation/modules/StakingAPIModule.html | 6 +- documentation/modules/StakingModule.html | 111 +- .../modules/StakingModule/dependencies.svg | 106 +- .../modules/StudioLegacyJwtModule.html | 8 +- .../StudioLegacyJwtModule/dependencies.svg | 8 +- documentation/modules/TokenModule.html | 8 +- .../modules/TokenModule/dependencies.svg | 8 +- documentation/modules/VoltageDexModule.html | 19 +- .../modules/VoltageDexModule/dependencies.svg | 8 +- documentation/modules/WebhooksModule.html | 42 +- .../modules/WebhooksModule/dependencies.svg | 42 +- documentation/overview.html | 1728 ++++---- 125 files changed, 23187 insertions(+), 5276 deletions(-) create mode 100644 documentation/classes/CreateChargeBridgeDto.html create mode 100644 documentation/classes/CreateOperatorCheckoutDto.html create mode 100644 documentation/classes/MultipleTokenPricesDto.html create mode 100644 documentation/controllers/StakingApiV2Controller.html create mode 100644 documentation/guards/CronGuard.html create mode 100644 documentation/injectables/LiquidStakingFuseClient.html create mode 100644 documentation/injectables/SimpleStakingService.html create mode 100644 documentation/injectables/VoltBarClient.html create mode 100644 documentation/interfaces/ChargeBridge.html create mode 100644 documentation/interfaces/ChargeCheckoutWebhookEvent.html create mode 100644 documentation/interfaces/ExplorerServiceCollectibleResponse.html create mode 100644 documentation/interfaces/ExplorerServiceGraphQLVariables.html create mode 100644 documentation/interfaces/ExplorerServiceTransformedCollectible.html create mode 100644 documentation/interfaces/NFTMetadata.html create mode 100644 documentation/interfaces/OperatorCheckout.html create mode 100644 documentation/interfaces/OperatorInvoice.html create mode 100644 documentation/interfaces/OperatorProject.html create mode 100644 documentation/interfaces/OperatorRefreshToken.html create mode 100644 documentation/interfaces/OperatorUser.html create mode 100644 documentation/interfaces/OperatorUserProjectResponse.html diff --git a/documentation/classes/AllExceptionsFilter.html b/documentation/classes/AllExceptionsFilter.html index e840b549..851b80c8 100644 --- a/documentation/classes/AllExceptionsFilter.html +++ b/documentation/classes/AllExceptionsFilter.html @@ -114,7 +114,7 @@

Constructor

- + @@ -190,8 +190,8 @@

- + @@ -271,6 +271,7 @@

import { ServerResponse } from 'http' import { MongoServerError } from 'mongodb' import { throwError } from 'rxjs' +import { get, isPlainObject } from 'lodash' @Catch() export class AllExceptionsFilter implements ExceptionFilter { @@ -299,8 +300,19 @@

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' @@ -315,7 +327,11 @@

} 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/documentation/classes/BatchTransaction.html b/documentation/classes/BatchTransaction.html index ab27eaa7..c03c5830 100644 --- a/documentation/classes/BatchTransaction.html +++ b/documentation/classes/BatchTransaction.html @@ -205,7 +205,7 @@

-
Defined in WalletAction:6 +
Defined in WalletAction:7
@@ -282,7 +282,7 @@

-
Defined in WalletAction:49 +
Defined in WalletAction:54
@@ -353,8 +353,8 @@

- + @@ -565,6 +565,7 @@

import { ERC_20_TYPE, ERC_721_TYPE, NATIVE_TOKEN_TYPE } from '@app/smart-wallets-service/common/constants/tokenTypes'
 import WalletAction from '@app/smart-wallets-service/data-layer/models/wallet-action/base'
 import { NATIVE_FUSE_TOKEN } from '@app/smart-wallets-service/common/constants/fuseTokenInfo'
+import { ethers } from 'ethers'
 
 export default class BatchTransaction extends WalletAction {
   descGenerator (data: any) {
@@ -585,14 +586,18 @@ 

const tokenData = await this.tokenService.fetchTokenDetails(functionDetail.targetAddress) if (tokenData.decimals === 0) { + const tokenIdObject = functionDetail.callData.find(item => + typeof item === 'object' && item._isBigNumber + ) + const tokenId = tokenIdObject ? ethers.BigNumber.from(tokenIdObject._hex).toString() : '0' return { type: ERC_721_TYPE, name: tokenData.name, symbol: tokenData.symbol, - decimals: '1', + decimals: 0, address: functionDetail.targetAddress, to: functionDetail.callData[0], - tokenId: functionDetail.callData[2], + tokenId, value: '0', actionName } diff --git a/documentation/classes/CreateChargeBridgeDto.html b/documentation/classes/CreateChargeBridgeDto.html new file mode 100644 index 00000000..b6d36e4c --- /dev/null +++ b/documentation/classes/CreateChargeBridgeDto.html @@ -0,0 +1,269 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
+
+ + +
+
+ + + + + + + + + + + + + + + +
+
+

+

File

+

+

+ apps/charge-accounts-service/src/operators/dto/create-charge-bridge.dto.ts +

+ + + + + + +
+

Index

+ + + + + + + + + + + + + + + +
+
Properties
+
+ +
+
+ + +
+ +

+ Properties +

+ + + + + + + + + + + + + + + + + +
+ + + amount + + +
+ Type : string + +
+ Decorators : +
+ + @IsString()
+
+
+ +
+ + + + + + + + + + + + + + + + + +
+ + + chainId + + +
+ Type : string + +
+ Decorators : +
+ + @IsString()
+
+
+ +
+
+ + + + + + + +
+ + +
+
import { IsString } from 'class-validator'
+
+export class CreateChargeBridgeDto {
+  @IsString()
+    chainId: string
+
+  @IsString()
+    amount: string
+}
+
+
+
+ + + + + + + + +
+
+

results matching ""

+
    +
    +
    +

    No results matching ""

    +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/classes/CreateOperatorCheckoutDto.html b/documentation/classes/CreateOperatorCheckoutDto.html new file mode 100644 index 00000000..d5939215 --- /dev/null +++ b/documentation/classes/CreateOperatorCheckoutDto.html @@ -0,0 +1,310 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
    +
    + + +
    +
    + + + + + + + + + + + + + + + +
    +
    +

    +

    File

    +

    +

    + apps/charge-accounts-service/src/operators/dto/create-operator-checkout.dto.ts +

    + + + + + + +
    +

    Index

    + + + + + + + + + + + + + + + +
    +
    Properties
    +
    + +
    +
    + + +
    + +

    + Properties +

    + + + + + + + + + + + + + + + + + +
    + + + billingCycle + + +
    + Type : string + +
    + Decorators : +
    + + @IsString()
    +
    +
    + +
    + + + + + + + + + + + + + + + + + +
    + + + cancelUrl + + +
    + Type : string + +
    + Decorators : +
    + + @IsString()
    +
    +
    + +
    + + + + + + + + + + + + + + + + + +
    + + + successUrl + + +
    + Type : string + +
    + Decorators : +
    + + @IsString()
    +
    +
    + +
    +
    + + + + + + + +
    + + +
    +
    import { IsString } from 'class-validator'
    +
    +export class CreateOperatorCheckoutDto {
    +  @IsString()
    +    successUrl: string
    +
    +  @IsString()
    +    cancelUrl: string
    +
    +  @IsString()
    +    billingCycle: string
    +}
    +
    +
    +
    + + + + + + + + +
    +
    +

    results matching ""

    +
      +
      +
      +

      No results matching ""

      +
      +
      +
      + +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/classes/MultipleTokenPricesDto.html b/documentation/classes/MultipleTokenPricesDto.html new file mode 100644 index 00000000..65ca06e6 --- /dev/null +++ b/documentation/classes/MultipleTokenPricesDto.html @@ -0,0 +1,228 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
      +
      + + +
      +
      + + + + + + + + + + + + + + + +
      +
      +

      +

      File

      +

      +

      + apps/charge-network-service/src/voltage-dex/dto/multiple-token-prices.dto.ts +

      + + + + + + +
      +

      Index

      + + + + + + + + + + + + + + + +
      +
      Properties
      +
      + +
      +
      + + +
      + +

      + Properties +

      + + + + + + + + + + + + + + + + + +
      + + + tokenAddresses + + +
      + Type : string[] + +
      + Decorators : +
      + + @IsEthereumAddress({each: true})
      +
      +
      + +
      +
      + + + + + + + +
      + + +
      +
      import { IsEthereumAddress } from 'class-validator'
      +
      +export class MultipleTokenPricesDto {
      +  @IsEthereumAddress({ each: true })
      +    tokenAddresses: string[]
      +}
      +
      +
      +
      + + + + + + + + +
      +
      +

      results matching ""

      +
        +
        +
        +

        No results matching ""

        +
        +
        +
        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/classes/ScannerService.html b/documentation/classes/ScannerService.html index d9f80b1e..18e79b64 100644 --- a/documentation/classes/ScannerService.html +++ b/documentation/classes/ScannerService.html @@ -412,7 +412,7 @@

        ) await this.scannerStatusService.updateStatus(toBlockNumber) } catch (error) { - // this.logger.error(`Failed to process blocks: ${error}`) + this.logger.error(`Failed to process blocks: ${error}`) } } } diff --git a/documentation/controllers/ApiKeysController-1.html b/documentation/controllers/ApiKeysController-1.html index 02ca6318..1cf14cb2 100644 --- a/documentation/controllers/ApiKeysController-1.html +++ b/documentation/controllers/ApiKeysController-1.html @@ -55,15 +55,9 @@

        File

        - apps/charge-apps-service/src/api-keys/api-keys.controller.ts + apps/charge-api-service/src/api-keys/api-keys.controller.ts

        -

        -

        Prefix

        -

        -

        - api-keys -

        @@ -89,12 +83,21 @@
        Methods
      • createPublic
      • +
      • + createSandbox +
      • createSecret
      • +
      • + getProjectIdByPublicKey +
      • getPublic
      • +
      • + getSandbox +
      • updateSecret
      • @@ -128,7 +131,7 @@

        -checkIfSecretExists(apiKeysDto: ApiKeysDto) +checkIfSecretExists(projectId: string) @@ -143,7 +146,7 @@

        + class="link-to-prism">apps/charge-api-service/src/api-keys/api-keys.controller.ts:26

        @@ -166,9 +169,9 @@

        - apiKeysDto + projectId - ApiKeysDto + string @@ -207,7 +210,7 @@

        -createPublic(apiKeysDto: ApiKeysDto) +createPublic(projectId: string) @@ -221,8 +224,8 @@

        - + @@ -245,9 +248,9 @@

        - apiKeysDto + projectId - ApiKeysDto + string @@ -273,6 +276,85 @@

        + + + + + + + + + + + + + + + + + + + + + + +
        + + + createSandbox + + +
        +createSandbox(projectId: string) +
        + Decorators : +
        + @MessagePattern('create_sandbox_key')
        +
        + +
        +

        Creates the sandBox API key associated with the project

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        projectId + string + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        +

        the sandBox API key associated with the given project

        + +
        +
        @@ -286,7 +368,7 @@

        @@ -301,7 +383,7 @@

        @@ -324,9 +406,9 @@

        - +
        -createSecret(apiKeysDto: ApiKeysDto) +createSecret(createSecretDto: CreateSecretDto)
        + class="link-to-prism">apps/charge-api-service/src/api-keys/api-keys.controller.ts:16
        apiKeysDtocreateSecretDto - ApiKeysDto + CreateSecretDto @@ -352,6 +434,85 @@

        + + + + + + + + + + + + + + + + + + + + + + +
        + + + getProjectIdByPublicKey + + +
        +getProjectIdByPublicKey(apiKey: string) +
        + Decorators : +
        + @MessagePattern('get_project_id_by_public_key')
        +
        + +
        +

        Gets the project id for the given public api key

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        apiKey + string + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        +

        an object consisting unsensitive fields of the api_keys of the project

        + +
        +
        @@ -365,7 +526,7 @@

        @@ -379,8 +540,8 @@

        @@ -403,9 +564,9 @@

        - +
        -getPublic(apiKeysDto: ApiKeysDto) +getPublic(projectId: string)
        - +
        apiKeysDtoprojectId - ApiKeysDto + string @@ -431,6 +592,82 @@

        + + + + + + + + + + + + + + + + + + + + + + +
        + + + getSandbox + + +
        +getSandbox(projectId: string) +
        + Decorators : +
        + @MessagePattern('get_sandbox_key')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        projectId + string + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        + +
        +
        @@ -444,7 +681,7 @@

        @@ -458,8 +695,8 @@

        @@ -482,9 +719,9 @@

        - + @@ -146,7 +143,7 @@

        @@ -169,9 +166,9 @@

        - + @@ -224,8 +221,8 @@

        @@ -248,9 +245,9 @@

        - +
        -updateSecret(apiKeysDto: ApiKeysDto) +updateSecret(projectId: string)
        - +
        apiKeysDtoprojectId - ApiKeysDto + string @@ -514,63 +751,88 @@

        -
        import { ApiKeysService } from '@app/apps-service/api-keys/api-keys.service'
        -import { Controller } from '@nestjs/common'
        +        
        import { Controller } from '@nestjs/common'
         import { MessagePattern } from '@nestjs/microservices'
        -import { ApiKeysDto } from '@app/apps-service/api-keys/dto/api-keys.dto'
        +import { ApiKeysService } from 'apps/charge-api-service/src/api-keys/api-keys.service'
        +import { CreateSecretDto } from '@app/api-service/api-keys/dto/secret-key.dto'
         
        -@Controller('api-keys')
        +@Controller()
         export class ApiKeysController {
           constructor (private readonly apiKeysService: ApiKeysService) { }
         
           /**
            * Creates an API key secret for the given project
        -   * @param apiKeysDto
        +   * @param projectId
            * @returns the generated API key secret or error if secret already exists
            */
           @MessagePattern('create_secret')
        -  createSecret (apiKeysDto: ApiKeysDto) {
        -    return this.apiKeysService.createSecretKey(apiKeysDto)
        +  createSecret (createSecretDto: CreateSecretDto) {
        +    return this.apiKeysService.createSecretKey(createSecretDto.projectId, createSecretDto.createLegacyAccount)
           }
         
           /**
            * Gets the api_key's for the given projectId
        -   * @param apiKeysDto
        +   * @param projectId
            * @returns an object consisting unsensitive fields of the api_keys of the project
            */
           @MessagePattern('get_api_keys_info')
        -  checkIfSecretExists (apiKeysDto: ApiKeysDto) {
        -    return this.apiKeysService.getApiKeysInfo(apiKeysDto)
        +  checkIfSecretExists (projectId: string) {
        +    return this.apiKeysService.getApiKeysInfo(projectId)
        +  }
        +
        +  /**
        +   * Gets the project id for the given public api key
        +   * @param publicApiKey
        +   * @returns an object consisting unsensitive fields of the api_keys of the project
        +   */
        +  @MessagePattern('get_project_id_by_public_key')
        +  getProjectIdByPublicKey (apiKey: string) {
        +    return this.apiKeysService.getProjectIdByPublicKey(apiKey)
           }
         
           /**
            * Revokes the old API key secret and generates a new one for the given project
        -   * @param apiKeysDto
        +   * @param projectId
            * @returns the new API key secret
            */
           @MessagePattern('update_secret')
        -  updateSecret (apiKeysDto: ApiKeysDto) {
        -    return this.apiKeysService.updateSecretKey(apiKeysDto)
        +  updateSecret (projectId: string) {
        +    return this.apiKeysService.updateSecretKey(projectId)
           }
         
           /**
            * Creates the public API key associated with the project
        -   * @param apiKeysDto
        +   * @param projectId
            * @returns the public API key associated with the given project
            */
           @MessagePattern('create_public')
        -  createPublic (apiKeysDto: ApiKeysDto) {
        -    return this.apiKeysService.createPublicKey(apiKeysDto)
        +  createPublic (projectId: string) {
        +    return this.apiKeysService.createPublicKey(projectId)
           }
         
           /**
            * Gets the public API key associated with the project
        -   * @param apiKeysDto
        +   * @param projectId
            * @returns the public API key associated with the given project
            */
           @MessagePattern('get_public')
        -  getPublic (apiKeysDto: ApiKeysDto) {
        -    return this.apiKeysService.getPublicKey(apiKeysDto)
        +  getPublic (projectId: string) {
        +    return this.apiKeysService.getPublicKey(projectId)
        +  }
        +
        +  /**
        +  * Creates the sandBox API key associated with the project
        +  * @param projectId
        +  * @returns the sandBox API key associated with the given project
        +  */
        +  @MessagePattern('create_sandbox_key')
        +  createSandbox (projectId: string) {
        +    return this.apiKeysService.createSandboxKey(projectId)
        +  }
        +
        +  @MessagePattern('get_sandbox_key')
        +  getSandbox (projectId: string) {
        +    return this.apiKeysService.getSandboxKey(projectId)
           }
         }
         
        diff --git a/documentation/controllers/ApiKeysController.html b/documentation/controllers/ApiKeysController.html index b7867bbd..0871390b 100644 --- a/documentation/controllers/ApiKeysController.html +++ b/documentation/controllers/ApiKeysController.html @@ -55,9 +55,15 @@

        File

        - apps/charge-api-service/src/api-keys/api-keys.controller.ts + apps/charge-apps-service/src/api-keys/api-keys.controller.ts

        +

        +

        Prefix

        +

        +

        + api-keys +

        @@ -83,21 +89,12 @@
        Methods
      • createPublic
      • -
      • - createSandbox -
      • createSecret
      • -
      • - getProjectIdByPublicKey -
      • getPublic
      • -
      • - getSandbox -
      • updateSecret
      • @@ -131,7 +128,7 @@

        -checkIfSecretExists(projectId: string) +checkIfSecretExists(apiKeysDto: ApiKeysDto)
        + class="link-to-prism">apps/charge-apps-service/src/api-keys/api-keys.controller.ts:26
        projectIdapiKeysDto - string + ApiKeysDto @@ -210,7 +207,7 @@

        -createPublic(projectId: string) +createPublic(apiKeysDto: ApiKeysDto)
        - +
        projectIdapiKeysDto - string + ApiKeysDto @@ -276,85 +273,6 @@

        - - - - - - - - - - - - - - - - - - - - - - -
        - - - createSandbox - - -
        -createSandbox(projectId: string) -
        - Decorators : -
        - @MessagePattern('create_sandbox_key')
        -
        - -
        -

        Creates the sandBox API key associated with the project

        -
        - -
        - Parameters : - - - - - - - - - - - - - - - - - - - -
        NameTypeOptional
        projectId - string - - No -
        -
        -
        -
        -
        - Returns : any - -
        -
        -

        the sandBox API key associated with the given project

        - -
        -
        @@ -368,7 +286,7 @@

        @@ -383,7 +301,7 @@

        @@ -406,9 +324,9 @@

        - +
        -createSecret(createSecretDto: CreateSecretDto) +createSecret(apiKeysDto: ApiKeysDto)
        + class="link-to-prism">apps/charge-apps-service/src/api-keys/api-keys.controller.ts:16
        createSecretDtoapiKeysDto - CreateSecretDto + ApiKeysDto @@ -434,85 +352,6 @@

        - - - - - - - - - - - - - - - - - - - - - - -
        - - - getProjectIdByPublicKey - - -
        -getProjectIdByPublicKey(apiKey: string) -
        - Decorators : -
        - @MessagePattern('get_project_id_by_public_key')
        -
        - -
        -

        Gets the project id for the given public api key

        -
        - -
        - Parameters : - - - - - - - - - - - - - - - - - - - -
        NameTypeOptional
        apiKey - string - - No -
        -
        -
        -
        -
        - Returns : any - -
        -
        -

        an object consisting unsensitive fields of the api_keys of the project

        - -
        -
        @@ -526,7 +365,7 @@

        @@ -540,8 +379,8 @@

        @@ -564,9 +403,9 @@

        - +
        -getPublic(projectId: string) +getPublic(apiKeysDto: ApiKeysDto)
        - +
        projectIdapiKeysDto - string + ApiKeysDto @@ -592,82 +431,6 @@

        - - - - - - - - - - - - - - - - - - - - - - -
        - - - getSandbox - - -
        -getSandbox(projectId: string) -
        - Decorators : -
        - @MessagePattern('get_sandbox_key')
        -
        - -
        - -
        - Parameters : - - - - - - - - - - - - - - - - - - - -
        NameTypeOptional
        projectId - string - - No -
        -
        -
        -
        -
        - Returns : any - -
        -
        - -
        -
        @@ -681,7 +444,7 @@

        @@ -695,8 +458,8 @@

        @@ -719,9 +482,9 @@

        - + @@ -221,8 +221,8 @@

        @@ -298,14 +298,16 @@

        -
        import { Controller, Get, Param, UseGuards, Query } from '@nestjs/common'
        +        
        import { Controller, Get, Param, UseGuards, Query, UseInterceptors } from '@nestjs/common'
         import { IsValidPublicApiKeyGuard } from '@app/api-service/api-keys/guards/is-valid-public-api-key.guard'
         import { BalancesAPIService } from '@app/api-service/balances-api/balances-api.service'
         import { ApiForbiddenResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags, getSchemaPath } from '@nestjs/swagger'
        +import { CacheInterceptor } from '@nestjs/cache-manager'
         
         @ApiTags('Balances - ERC20 & NFT')
         @UseGuards(IsValidPublicApiKeyGuard)
         @Controller('v0/balances')
        +@UseInterceptors(CacheInterceptor)
         export class BalancesAPIController {
           constructor (
             private readonly balancesAPIService: BalancesAPIService
        diff --git a/documentation/controllers/DataLayerController.html b/documentation/controllers/DataLayerController.html
        index 20a25d09..1c0ed94b 100644
        --- a/documentation/controllers/DataLayerController.html
        +++ b/documentation/controllers/DataLayerController.html
        @@ -83,6 +83,9 @@ 
        Methods
      • get
      • +
      • + getOperatorByApiKey +
      • handleTokenTransferWebhook
      • @@ -261,6 +264,82 @@

        -updateSecret(projectId: string) +updateSecret(apiKeysDto: ApiKeysDto)
        - +
        projectIdapiKeysDto - string + ApiKeysDto @@ -751,88 +514,63 @@

        -
        import { Controller } from '@nestjs/common'
        +        
        import { ApiKeysService } from '@app/apps-service/api-keys/api-keys.service'
        +import { Controller } from '@nestjs/common'
         import { MessagePattern } from '@nestjs/microservices'
        -import { ApiKeysService } from 'apps/charge-api-service/src/api-keys/api-keys.service'
        -import { CreateSecretDto } from '@app/api-service/api-keys/dto/secret-key.dto'
        +import { ApiKeysDto } from '@app/apps-service/api-keys/dto/api-keys.dto'
         
        -@Controller()
        +@Controller('api-keys')
         export class ApiKeysController {
           constructor (private readonly apiKeysService: ApiKeysService) { }
         
           /**
            * Creates an API key secret for the given project
        -   * @param projectId
        +   * @param apiKeysDto
            * @returns the generated API key secret or error if secret already exists
            */
           @MessagePattern('create_secret')
        -  createSecret (createSecretDto: CreateSecretDto) {
        -    return this.apiKeysService.createSecretKey(createSecretDto.projectId, createSecretDto.createLegacyAccount)
        +  createSecret (apiKeysDto: ApiKeysDto) {
        +    return this.apiKeysService.createSecretKey(apiKeysDto)
           }
         
           /**
            * Gets the api_key's for the given projectId
        -   * @param projectId
        +   * @param apiKeysDto
            * @returns an object consisting unsensitive fields of the api_keys of the project
            */
           @MessagePattern('get_api_keys_info')
        -  checkIfSecretExists (projectId: string) {
        -    return this.apiKeysService.getApiKeysInfo(projectId)
        -  }
        -
        -  /**
        -   * Gets the project id for the given public api key
        -   * @param publicApiKey
        -   * @returns an object consisting unsensitive fields of the api_keys of the project
        -   */
        -  @MessagePattern('get_project_id_by_public_key')
        -  getProjectIdByPublicKey (apiKey: string) {
        -    return this.apiKeysService.getProjectIdByPublicKey(apiKey)
        +  checkIfSecretExists (apiKeysDto: ApiKeysDto) {
        +    return this.apiKeysService.getApiKeysInfo(apiKeysDto)
           }
         
           /**
            * Revokes the old API key secret and generates a new one for the given project
        -   * @param projectId
        +   * @param apiKeysDto
            * @returns the new API key secret
            */
           @MessagePattern('update_secret')
        -  updateSecret (projectId: string) {
        -    return this.apiKeysService.updateSecretKey(projectId)
        +  updateSecret (apiKeysDto: ApiKeysDto) {
        +    return this.apiKeysService.updateSecretKey(apiKeysDto)
           }
         
           /**
            * Creates the public API key associated with the project
        -   * @param projectId
        +   * @param apiKeysDto
            * @returns the public API key associated with the given project
            */
           @MessagePattern('create_public')
        -  createPublic (projectId: string) {
        -    return this.apiKeysService.createPublicKey(projectId)
        +  createPublic (apiKeysDto: ApiKeysDto) {
        +    return this.apiKeysService.createPublicKey(apiKeysDto)
           }
         
           /**
            * Gets the public API key associated with the project
        -   * @param projectId
        +   * @param apiKeysDto
            * @returns the public API key associated with the given project
            */
           @MessagePattern('get_public')
        -  getPublic (projectId: string) {
        -    return this.apiKeysService.getPublicKey(projectId)
        -  }
        -
        -  /**
        -  * Creates the sandBox API key associated with the project
        -  * @param projectId
        -  * @returns the sandBox API key associated with the given project
        -  */
        -  @MessagePattern('create_sandbox_key')
        -  createSandbox (projectId: string) {
        -    return this.apiKeysService.createSandboxKey(projectId)
        -  }
        -
        -  @MessagePattern('get_sandbox_key')
        -  getSandbox (projectId: string) {
        -    return this.apiKeysService.getSandboxKey(projectId)
        +  getPublic (apiKeysDto: ApiKeysDto) {
        +    return this.apiKeysService.getPublicKey(apiKeysDto)
           }
         }
         
        diff --git a/documentation/controllers/BalancesAPIController.html b/documentation/controllers/BalancesAPIController.html index e9ab29e0..83944f3e 100644 --- a/documentation/controllers/BalancesAPIController.html +++ b/documentation/controllers/BalancesAPIController.html @@ -133,8 +133,8 @@

        - +
        - +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + getOperatorByApiKey + + +
        +getOperatorByApiKey(apiKey: string) +
        + Decorators : +
        + @MessagePattern('get-operator-by-api-key')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        apiKey + string + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        + +
        +
        @@ -350,7 +429,7 @@

        @@ -386,9 +465,9 @@

        - + + + + + + + + + + + + + + + + + + + +
        -sponsoredTransactionsCount(sponsorId: string) +sponsoredTransactionsCount(data: literal type)
        sponsorIddata - string + literal type @@ -526,11 +605,17 @@

        } @MessagePattern('sponsored-transactions-count') - sponsoredTransactionsCount (sponsorId: string) { + sponsoredTransactionsCount (data: { apiKey: string, startDate?: string }) { return this.dataLayerService.findSponsoredTransactionsCount( - sponsorId + data.apiKey, + data.startDate ) } + + @MessagePattern('get-operator-by-api-key') + getOperatorByApiKey (apiKey: string) { + return this.dataLayerService.getOperatorByApiKey(apiKey) + } } diff --git a/documentation/controllers/OperatorsController.html b/documentation/controllers/OperatorsController.html index 3b968c5d..826e971a 100644 --- a/documentation/controllers/OperatorsController.html +++ b/documentation/controllers/OperatorsController.html @@ -81,14 +81,30 @@

        Methods
        Async checkOperatorExistence +
      • + Async + checkoutSession +
      • Async checkWalletActivationStatus
      • +
      • + Async + createChargeBridge +
      • Async createOperatorUserAndProjectAndWallet
      • +
      • + Async + createOperatorWallet +
      • +
      • + Async + createSubscription +
      • Async findOperatorByOwnerId @@ -97,6 +113,10 @@
        Methods
        Async findOperatorBySmartWallet
      • +
      • + Async + getCheckoutSessions +
      • Async getOperatorsUserAndProject @@ -105,10 +125,29 @@
        Methods
        Async getSponsoredTransactionsCount
      • +
      • + Async + getSubscriptions +
      • +
      • + Async + handleCheckoutWebhook +
      • Async handleWebhookReceiveAndFundPaymaster
      • +
      • + Async + migrateOperatorWallet +
      • +
      • + Async + processMonthlySubscriptions +
      • +
      • + refreshToken +
      • validate
      • @@ -133,18 +172,812 @@

        - + + + Async + checkOperatorExistence + + +
        + + checkOperatorExistence(address: string, response: Response) +
        + Decorators : +
        + @Head('/eoaAddress/:address')
        @ApiOperation({summary: 'Check if operator exist'})
        @ApiParam({name: 'address', type: String, required: true})
        @ApiCreatedResponse({description: 'Operator exist'})
        @ApiNotFoundResponse({description: 'Operator does not exist'})
        +
        + +
        +

        Check if operator exist

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        address + string + + No +
        response + Response + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + checkoutSession + + +
        + + checkoutSession(auth0Id: string, createOperatorCheckoutDto: CreateOperatorCheckoutDto) +
        + Decorators : +
        + @UseGuards(JwtAuthGuard)
        @Post('/checkout/sessions')
        @ApiOperation({summary: 'Create a checkout session for the operator'})
        +
        + +
        +

        Create a checkout session for the operator

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        auth0Id + string + + No +
        createOperatorCheckoutDto + CreateOperatorCheckoutDto + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        +

        the checkout URL

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + checkWalletActivationStatus + + +
        + + checkWalletActivationStatus(auth0Id: string, response: Response) +
        + Decorators : +
        + @UseGuards(JwtAuthGuard)
        @Get('/is-activated')
        @ApiOperation({summary: 'Check if operator wallet is activated'})
        @ApiCreatedResponse({description: 'Wallet is activated'})
        @ApiNotFoundResponse({description: 'Wallet not activated'})
        +
        + +
        +

        Check if operator wallet is activated

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        auth0Id + string + + No +
        response + Response + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        +

        OK if operator wallet is activated, not found otherwise

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + createChargeBridge + + +
        + + createChargeBridge(auth0Id: string, createChargeBridgeDto: CreateChargeBridgeDto) +
        + Decorators : +
        + @UseGuards(JwtAuthGuard)
        @Post('/bridge')
        @ApiOperation({summary: 'Create a Charge bridge wallet address for operator deposit'})
        +
        + +
        +

        Create a Charge bridge wallet address for operator deposit

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        auth0Id + string + + No +
        createChargeBridgeDto + CreateChargeBridgeDto + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        +

        the Charge bridge deposit wallet address

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + createOperatorUserAndProjectAndWallet + + +
        + + createOperatorUserAndProjectAndWallet(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) +
        + Decorators : +
        + @UseGuards(JwtAuthGuard)
        @Post('/account')
        @ApiOperation({summary: 'Create user, project and AA wallet for an operator'})
        @ApiBody({type: CreateOperatorUser, required: true})
        +
        + +
        +

        Create user and project for an operator

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        createOperatorUserDto + CreateOperatorUserDto + + No +
        auth0Id + string + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        +

        the user and project with public key

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + createOperatorWallet + + +
        + + createOperatorWallet(createOperatorWalletDto: CreateOperatorWalletDto, auth0Id: string) +
        + Decorators : +
        + @UseGuards(JwtAuthGuard)
        @Post('/wallet')
        @ApiOperation({summary: 'Create AA wallet for an operator'})
        @ApiBody({type: CreateOperatorWallet, required: true})
        +
        + +
        +

        Create AA wallet for an operator

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        createOperatorWalletDto + CreateOperatorWalletDto + + No +
        auth0Id + string + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        +

        the AA wallet

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + createSubscription + + +
        + + createSubscription(auth0Id: string) +
        + Decorators : +
        + @UseGuards(JwtAuthGuard)
        @Post('/subscriptions')
        @ApiOperation({summary: 'Create a subscription for the operator'})
        +
        + +
        +

        Create a subscription for the operator

        +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        auth0Id + string + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        +

        the subscription

        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + findOperatorByOwnerId + + +
        + + findOperatorByOwnerId(walletAddress: string) +
        + Decorators : +
        + @MessagePattern('find-operator-by-owner-id')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        walletAddress + string + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + Async + findOperatorBySmartWallet + + +
        + + findOperatorBySmartWallet(walletAddress: string) +
        + Decorators : +
        + @MessagePattern('find-operator-by-smart-wallet')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        walletAddress + string + + No +
        +
        +
        +
        +
        + Returns : unknown + +
        +
        + +
        +
        + + + + @@ -152,21 +985,21 @@

        - + @@ -192,18 +1025,6 @@

        -

        - - - - - - -
        + Async - checkOperatorExistence - + getCheckoutSessions +
        - checkOperatorExistence(address: string, response: Response) + getCheckoutSessions(auth0Id: string)
        Decorators :
        - @Head('/eoaAddress/:address')
        @ApiOperation({summary: 'Check if operator exist'})
        @ApiParam({name: 'address', type: String, required: true})
        @ApiCreatedResponse({description: 'Operator exist'})
        @ApiNotFoundResponse({description: 'Operator does not exist'})
        + @UseGuards(JwtAuthGuard)
        @Get('/checkout/sessions')
        @ApiOperation({summary: 'Get all checkout sessions for the operator'})
        - +
        -

        Check if operator exist

        +

        Get all checkout sessions for the operator

        @@ -182,7 +1015,7 @@

        addressauth0Id string
        response - Response - - No -
        @@ -211,11 +1032,12 @@

        - Returns : any + Returns : unknown
        - +

        checkout sessions

        +
        @@ -225,18 +1047,18 @@

        - + Async - checkWalletActivationStatus - + getOperatorsUserAndProject + - checkWalletActivationStatus(auth0Id: string, response: Response) + getOperatorsUserAndProject(auth0Id: string) @@ -244,21 +1066,21 @@

        Decorators :
        - @UseGuards(JwtAuthGuard)
        @Get('/is-activated')
        @ApiOperation({summary: 'Check if operator wallet is activated'})
        @ApiCreatedResponse({description: 'Wallet is activated'})
        @ApiNotFoundResponse({description: 'Wallet not activated'})
        + @UseGuards(JwtAuthGuard)
        @Get('/account')
        @ApiOperation({summary: 'Get current operator'})
        @ApiCreatedResponse({description: 'The operator has been successfully fetched.'})
        - + -

        Check if operator wallet is activated

        +

        Get current operator

        @@ -284,18 +1106,6 @@

        - - - response - - Response - - - - No - - - @@ -307,7 +1117,7 @@

        -

        OK if operator wallet is activated, not found otherwise

        +

        the user and project with public key

        @@ -318,18 +1128,18 @@

        - + Async - createOperatorUserAndProjectAndWallet - + getSponsoredTransactionsCount + - createOperatorUserAndProjectAndWallet(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) + getSponsoredTransactionsCount(auth0Id: string) @@ -337,21 +1147,21 @@

        Decorators :
        - @UseGuards(JwtAuthGuard)
        @Post('/account')
        @ApiOperation({summary: 'Create user, project and AA wallet for an operator'})
        @ApiBody({type: CreateOperatorUser, required: true})
        + @UseGuards(JwtAuthGuard)
        @Get('/sponsored-transaction')
        @ApiOperation({summary: 'Get sponsored transactions count'})
        @ApiCreatedResponse({description: 'The sponsored transactions count has been successfully fetched.'})
        - + -

        Create user, project and AA wallet for an operator

        +

        Get sponsored transactions count

        @@ -366,18 +1176,6 @@

        - - createOperatorUserDto - - CreateOperatorUserDto - - - - No - - - - auth0Id @@ -400,7 +1198,7 @@

        -

        the user, project and AA wallet with public key

        +

        sponsored transactions count

        @@ -411,18 +1209,18 @@

        - + Async - findOperatorByOwnerId - + getSubscriptions + - findOperatorByOwnerId(walletAddress: string) + getSubscriptions(auth0Id: string) @@ -430,20 +1228,22 @@

        Decorators :
        - @MessagePattern('find-operator-by-owner-id')
        + @UseGuards(JwtAuthGuard)
        @Get('/subscriptions')
        @ApiOperation({summary: 'Get all subscription invoices for the operator'})
        - + +

        Get all subscription invoices for the operator

        +
        Parameters : @@ -458,7 +1258,7 @@

        - walletAddress + auth0Id string @@ -479,7 +1279,8 @@

        - +

        the subscription invoices

        +
        @@ -489,18 +1290,18 @@

        - + Async - findOperatorBySmartWallet - + handleCheckoutWebhook + - findOperatorBySmartWallet(walletAddress: string) + handleCheckoutWebhook(webhookEvent: ChargeCheckoutWebhookEvent) @@ -508,20 +1309,22 @@

        Decorators :
        - @MessagePattern('find-operator-by-smart-wallet')
        + @Post('/checkout/webhook')
        @ApiOperation({summary: 'Handle the checkout webhook'})
        - + +

        Handle the checkout webhook

        +
        Parameters : @@ -536,9 +1339,9 @@

        - walletAddress + webhookEvent - string + ChargeCheckoutWebhookEvent @@ -567,18 +1370,18 @@

        - + Async - getOperatorsUserAndProject - + handleWebhookReceiveAndFundPaymaster + - getOperatorsUserAndProject(auth0Id: string) + handleWebhookReceiveAndFundPaymaster(webhookEvent: WebhookEvent) @@ -586,21 +1389,21 @@

        Decorators :
        - @UseGuards(JwtAuthGuard)
        @Get('/account')
        @ApiOperation({summary: 'Get current operator'})
        @ApiCreatedResponse({description: 'The operator has been successfully fetched.'})
        + @Post('/webhook/fund')
        @ApiOperation({summary: 'Handle Webhook Receive And Fund Paymaster'})
        @ApiCreatedResponse({description: 'The webhook event has been successfully handled.'})
        - + -

        Get current operator

        +

        Handle Webhook Receive And Fund Paymaster

        @@ -616,9 +1419,9 @@

        - auth0Id + webhookEvent - string + WebhookEvent @@ -637,8 +1440,7 @@

        -

        the user and project with public key

        - +
        @@ -648,18 +1450,18 @@

        - + Async - getSponsoredTransactionsCount - + migrateOperatorWallet + - getSponsoredTransactionsCount(auth0Id: string) + migrateOperatorWallet(migrateOperatorWalletDto: CreateOperatorWalletDto, auth0Id: string) @@ -667,21 +1469,21 @@

        Decorators :
        - @UseGuards(JwtAuthGuard)
        @Get('/sponsored-transaction')
        @ApiOperation({summary: 'Get sponsored transactions count'})
        @ApiCreatedResponse({description: 'The sponsored transactions count has been successfully fetched.'})
        + @UseGuards(JwtAuthGuard)
        @Post('/migrate-wallet')
        @ApiOperation({summary: 'Migrate Etherspot AA wallet to SAFE for an operator'})
        @ApiBody({type: CreateOperatorWallet, required: true})
        - + -

        Get sponsored transactions count

        +

        Migrate Etherspot AA wallet to SAFE for an operator

        @@ -696,6 +1498,18 @@

        + + migrateOperatorWalletDto + + CreateOperatorWalletDto + + + + No + + + + auth0Id @@ -718,7 +1532,7 @@

        -

        sponsored transactions count

        +

        the SAFE AA wallet

        @@ -729,18 +1543,18 @@

        - + Async - handleWebhookReceiveAndFundPaymaster - + processMonthlySubscriptions + - handleWebhookReceiveAndFundPaymaster(webhookEvent: WebhookEvent) + processMonthlySubscriptions() @@ -748,21 +1562,67 @@

        Decorators :
        - @Post('/webhook/fund')
        @ApiOperation({summary: 'Handle Webhook Receive And Fund Paymaster'})
        @ApiCreatedResponse({description: 'The webhook event has been successfully handled.'})
        + @UseGuards(CronGuard)
        @Post('/subscriptions/process')
        @ApiOperation({summary: 'Trigger the process of monthly subscriptions'})
        - + -

        Handle Webhook Receive And Fund Paymaster

        +

        Trigger the process of monthly subscriptions

        +
        + +
        + Returns : unknown + +
        + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + @@ -818,7 +1691,7 @@

        @@ -832,8 +1705,8 @@

        @@ -866,6 +1739,18 @@

        + +

        + + + + + +
        + + + refreshToken + + +
        +refreshToken(request: Request, response: Response) +
        + Decorators : +
        + @Post('/refresh-token')
        @ApiOperation({summary: 'Refresh operator token'})
        +
        + +
        +

        Refresh operator token

        @@ -778,9 +1638,21 @@

        webhookEventrequest - WebhookEvent + Request + + No +
        response + Response @@ -795,11 +1667,12 @@

        - Returns : unknown + Returns : any
        - +

        new access and refresh JWTs of the operator

        +

        -validate(authOperatorDto: AuthOperatorDto) +validate(authOperatorDto: AuthOperatorDto, response: Response)
        - +
        response + Response + + No +
        @@ -888,18 +1773,24 @@

        -
        import { Body, Controller, Get, Head, Logger, Param, Post, Res, UseGuards } from '@nestjs/common'
        +        
        import { Body, Controller, Get, Head, Logger, Param, Post, Req, Res, UseGuards } from '@nestjs/common'
         import { User } from '@app/accounts-service/users/user.decorator'
         import { JwtAuthGuard } from '@app/accounts-service/auth/guards/jwt-auth.guard'
         import { CreateOperatorUserDto } from '@app/accounts-service/operators/dto/create-operator-user.dto'
         import { OperatorsService } from '@app/accounts-service/operators/operators.service'
         import { AuthOperatorDto } from '@app/accounts-service/operators/dto/auth-operator.dto'
        -import { Response } from 'express'
        +import { Request, Response } from 'express'
         import { WebhookEvent } from '@app/apps-service/payments/interfaces/webhook-event.interface'
         import { MessagePattern } from '@nestjs/microservices'
         import { ApiBody, ApiCreatedResponse, ApiNotFoundResponse, ApiOperation, ApiParam, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'
         import { AuthOperator } from '@app/accounts-service/operators/entities/auth-operator.entity'
         import { CreateOperatorUser } from '@app/accounts-service/operators/entities/create-operator-user.entity'
        +import { CreateOperatorWalletDto } from '@app/accounts-service/operators/dto/create-operator-wallet.dto'
        +import { CreateOperatorCheckoutDto } from '@app/accounts-service/operators/dto/create-operator-checkout.dto'
        +import { ChargeCheckoutWebhookEvent } from '@app/accounts-service/operators/interfaces/charge-checkout-webhook-event.interface'
        +import { CreateChargeBridgeDto } from '@app/accounts-service/operators/dto/create-charge-bridge.dto'
        +import { CreateOperatorWallet } from '@app/accounts-service/operators/entities/create-operator-wallet.entity'
        +import { CronGuard } from '@app/accounts-service/auth/guards/cron.guard'
         
         @ApiTags('Operators')
         @Controller({ path: 'operators', version: '1' })
        @@ -933,8 +1824,8 @@ 

        @ApiBody({ type: AuthOperator, required: true }) @ApiCreatedResponse({ description: 'The operator has been successfully validated.' }) @ApiUnauthorizedResponse({ description: 'Unauthorized.' }) - validate (@Body() authOperatorDto: AuthOperatorDto) { - return this.operatorsService.validate(authOperatorDto) + validate (@Body() authOperatorDto: AuthOperatorDto, @Res({ passthrough: true }) response: Response) { + return this.operatorsService.validate(authOperatorDto, response) } /** @@ -951,9 +1842,9 @@

        } /** - * Create user, project and AA wallet for an operator + * Create user and project for an operator * @param authOperatorDto - * @returns the user, project and AA wallet with public key + * @returns the user and project with public key */ @UseGuards(JwtAuthGuard) @Post('/account') @@ -963,6 +1854,32 @@

        return this.operatorsService.createOperatorUserAndProjectAndWallet(createOperatorUserDto, auth0Id) } + /** + * Create AA wallet for an operator + * @param authOperatorDto + * @returns the AA wallet + */ + @UseGuards(JwtAuthGuard) + @Post('/wallet') + @ApiOperation({ summary: 'Create AA wallet for an operator' }) + @ApiBody({ type: CreateOperatorWallet, required: true }) + async createOperatorWallet (@Body() createOperatorWalletDto: CreateOperatorWalletDto, @User('sub') auth0Id: string) { + return this.operatorsService.createOperatorWallet(createOperatorWalletDto, auth0Id) + } + + /** + * Migrate Etherspot AA wallet to SAFE for an operator + * @param authOperatorDto + * @returns the SAFE AA wallet + */ + @UseGuards(JwtAuthGuard) + @Post('/migrate-wallet') + @ApiOperation({ summary: 'Migrate Etherspot AA wallet to SAFE for an operator' }) + @ApiBody({ type: CreateOperatorWallet, required: true }) + async migrateOperatorWallet (@Body() migrateOperatorWalletDto: CreateOperatorWalletDto, @User('sub') auth0Id: string) { + return this.operatorsService.migrateOperatorWallet(migrateOperatorWalletDto, auth0Id) + } + /** * Handle Webhook Receive And Fund Paymaster */ @@ -1013,6 +1930,92 @@

        async findOperatorByOwnerId (walletAddress: string) { return this.operatorsService.findWalletOwner(walletAddress) } + + /** + * Refresh operator token + * @returns new access and refresh JWTs of the operator + */ + @Post('/refresh-token') + @ApiOperation({ summary: 'Refresh operator token' }) + refreshToken (@Req() request: Request, @Res({ passthrough: true }) response: Response) { + return this.operatorsService.validateRefreshToken(request.cookies?.operator_refresh_token, response) + } + + /** + * Create a subscription for the operator + * @param createSubscriptionDto + * @returns the subscription + */ + @UseGuards(JwtAuthGuard) + @Post('/subscriptions') + @ApiOperation({ summary: 'Create a subscription for the operator' }) + async createSubscription (@User('sub') auth0Id: string) { + return this.operatorsService.createSubscription(auth0Id) + } + + /** + * Get all subscription invoices for the operator + * @returns the subscription invoices + */ + @UseGuards(JwtAuthGuard) + @Get('/subscriptions') + @ApiOperation({ summary: 'Get all subscription invoices for the operator' }) + async getSubscriptions (@User('sub') auth0Id: string) { + return this.operatorsService.getSubscriptions(auth0Id) + } + + /** + * Create a checkout session for the operator + * @returns the checkout URL + */ + @UseGuards(JwtAuthGuard) + @Post('/checkout/sessions') + @ApiOperation({ summary: 'Create a checkout session for the operator' }) + async checkoutSession (@User('sub') auth0Id: string, @Body() createOperatorCheckoutDto: CreateOperatorCheckoutDto) { + return this.operatorsService.checkout(auth0Id, createOperatorCheckoutDto) + } + + /** + * Get all checkout sessions for the operator + * @returns checkout sessions + */ + @UseGuards(JwtAuthGuard) + @Get('/checkout/sessions') + @ApiOperation({ summary: 'Get all checkout sessions for the operator' }) + async getCheckoutSessions (@User('sub') auth0Id: string) { + return this.operatorsService.getCheckoutSessions(auth0Id) + } + + /** + * Handle the checkout webhook + */ + @Post('/checkout/webhook') + @ApiOperation({ summary: 'Handle the checkout webhook' }) + async handleCheckoutWebhook (@Body() webhookEvent: ChargeCheckoutWebhookEvent) { + return this.operatorsService.handleCheckoutWebhook(webhookEvent) + } + + /** + * Create a Charge bridge wallet address for operator deposit + * @param chargeBridgeDto + * @returns the Charge bridge deposit wallet address + */ + @UseGuards(JwtAuthGuard) + @Post('/bridge') + @ApiOperation({ summary: 'Create a Charge bridge wallet address for operator deposit' }) + async createChargeBridge (@User('sub') auth0Id: string, @Body() createChargeBridgeDto: CreateChargeBridgeDto) { + return this.operatorsService.createChargeBridge(auth0Id, createChargeBridgeDto) + } + + /** + * Trigger the process of monthly subscriptions + */ + @UseGuards(CronGuard) + @Post('/subscriptions/process') + @ApiOperation({ summary: 'Trigger the process of monthly subscriptions' }) + async processMonthlySubscriptions () { + return this.operatorsService.processMonthlySubscriptions() + } }

        diff --git a/documentation/controllers/StakingApiV2Controller.html b/documentation/controllers/StakingApiV2Controller.html new file mode 100644 index 00000000..91187dac --- /dev/null +++ b/documentation/controllers/StakingApiV2Controller.html @@ -0,0 +1,491 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
        +
        + + +
        +
        + + + + + + + + + +
        +
        +

        +

        File

        +

        +

        + apps/charge-api-service/src/staking-api/staking-api-v2.controller.ts +

        + + + + + + + +
        +

        Index

        + + + + + + + + + + + + + + + +
        +
        Methods
        +
        + +
        +
        + +
        + +

        + Methods +

        + + + + + + + + + + + + + + + + + + + + + + +
        + + + stake + + +
        +stake(stakeDto: StakeDto) +
        + Decorators : +
        + @Post('stake')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        stakeDto + StakeDto + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + stakedTokens + + +
        +stakedTokens(accountAddress: string) +
        + Decorators : +
        + @Get('staked_tokens/:accountAddress')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        accountAddress + string + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + stakingOptions + + +
        +stakingOptions() +
        + Decorators : +
        + @Get('staking_options')
        +
        + +
        + +
        + Returns : any + +
        +
        + + + + + + + + + + + + + + + + + + + + + + +
        + + + unstake + + +
        +unstake(unstakeDto: UnstakeDto) +
        + Decorators : +
        + @Post('unstake')
        +
        + +
        + +
        + Parameters : + + + + + + + + + + + + + + + + + + + +
        NameTypeOptional
        unstakeDto + UnstakeDto + + No +
        +
        +
        +
        +
        + Returns : any + +
        +
        + +
        +
        +
        + + +
        +
        import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'
        +import { IsValidPublicApiKeyGuard } from '@app/api-service/api-keys/guards/is-valid-public-api-key.guard'
        +import { StakingAPIService } from '@app/api-service/staking-api/staking-api.service'
        +import { UnstakeDto } from '@app/network-service/staking/dto/unstake.dto'
        +import { StakeDto } from '@app/network-service/staking/dto/stake.dto'
        +
        +@UseGuards(IsValidPublicApiKeyGuard)
        +@Controller({ path: 'staking', version: '2' })
        +export class StakingApiV2Controller {
        +  constructor (private readonly stakingAPIService: StakingAPIService) { }
        +
        +  @Get('staking_options')
        +  stakingOptions () {
        +    return this.stakingAPIService.stakingOptionsV2()
        +  }
        +
        +  @Post('stake')
        +  stake (@Body() stakeDto: StakeDto) {
        +    return this.stakingAPIService.stakeV2(stakeDto)
        +  }
        +
        +  @Post('unstake')
        +  unstake (@Body() unstakeDto: UnstakeDto) {
        +    return this.stakingAPIService.unStakeV2(unstakeDto)
        +  }
        +
        +  @Get('staked_tokens/:accountAddress')
        +  stakedTokens (@Param('accountAddress') accountAddress: string) {
        +    return this.stakingAPIService.stakedTokensV2(accountAddress)
        +  }
        +}
        +
        +
        +
        + + + + + + + + + + + + + + +
        +
        +

        results matching ""

        +
          +
          +
          +

          No results matching ""

          +
          +
          +
          + +
          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/controllers/StakingController.html b/documentation/controllers/StakingController.html index 9b6f5a94..0ce30fa4 100644 --- a/documentation/controllers/StakingController.html +++ b/documentation/controllers/StakingController.html @@ -89,12 +89,24 @@
          Methods
        • stakedTokens
        • +
        • + stakedTokensV2 +
        • +
        • + stakeV2 +
        • stakingOptions
        • +
        • + stakingOptionsV2 +
        • unstake
        • +
        • + unstakeV2 +
        • @@ -139,8 +151,8 @@

          - + @@ -215,8 +227,84 @@

          - + + + + + + + + +
          + Parameters : + + + + + + + + + + + + + + + + + + + +
          NameTypeOptional
          accountAddress + string + + No +
          +
          +
          +
          +
          + Returns : any + +
          +
          + +
          + + + + + + + + + + + + + + + + + + + @@ -264,6 +352,82 @@

          + + + stakedTokensV2 + + +
          +stakedTokensV2(accountAddress: string) +
          + Decorators : +
          + @MessagePattern('staked_tokens_v2')
          +
          +
          + + + + + + + + + + + + + + + + + + + + + + +
          + + + stakeV2 + + +
          +stakeV2(stakeDto: StakeDto) +
          + Decorators : +
          + @MessagePattern('stake_v2')
          +
          + +
          + +
          + Parameters : + + + + + + + + + + + + + + + + + + + +
          NameTypeOptional
          stakeDto + StakeDto + + No +
          +
          +
          +
          +
          + Returns : any + +
          +
          + +
          +
          @@ -297,6 +461,50 @@

          + + + + +
          + +
          + Returns : any + +
          +
          + + + + + + + + + + + + + + + + + + + + + + + + + +
          + + + stakingOptionsV2 + + +
          +stakingOptionsV2() +
          + Decorators : +
          + @MessagePattern('staking_options_v2')
          +
          + +
          @@ -335,8 +543,84 @@

          - + +
          + +
          + Parameters : + + + + + + + + + + + + + + + + + + + +
          NameTypeOptional
          unstakeDto + UnstakeDto + + No +
          +
          +
          +
          +
          + Returns : any + +
          +
          + +
          +
          + + + + + + + + + + + + + + + @@ -396,27 +680,47 @@

          @Controller('stake') export class StakingController { - constructor (private readonly stakingService: StakingService) { } + constructor (private readonly stakingService: StakingService) {} @MessagePattern('staking_options') stakingOptions () { return this.stakingService.stakingOptions() } + @MessagePattern('staking_options_v2') + stakingOptionsV2 () { + return this.stakingService.stakingOptionsV2() + } + @MessagePattern('stake') stake (stakeDto: StakeDto) { return this.stakingService.stake(stakeDto) } + @MessagePattern('stake_v2') + stakeV2 (stakeDto: StakeDto) { + return this.stakingService.stakeV2(stakeDto) + } + @MessagePattern('unstake') unstake (unstakeDto: UnstakeDto) { return this.stakingService.unStake(unstakeDto) } + @MessagePattern('unstake_v2') + unstakeV2 (unstakeDto: UnstakeDto) { + return this.stakingService.unStakeV2(unstakeDto) + } + @MessagePattern('staked_tokens') stakedTokens (accountAddress: string) { return this.stakingService.stakedTokens(accountAddress) } + + @MessagePattern('staked_tokens_v2') + stakedTokensV2 (accountAddress: string) { + return this.stakingService.stakedTokensV2(accountAddress) + } } diff --git a/documentation/controllers/TradeApiController.html b/documentation/controllers/TradeApiController.html index af6a9dc9..f8ee4cc7 100644 --- a/documentation/controllers/TradeApiController.html +++ b/documentation/controllers/TradeApiController.html @@ -77,6 +77,10 @@

          Methods
          + + +
          + + + unstakeV2 + + +
          +unstakeV2(unstakeDto: UnstakeDto) +
          + Decorators : +
          + @MessagePattern('unstake_v2')
          +
          +
            +
          • + Async + getMultipleTokenPrices +
          • Async getTokenHistoricalStatistics @@ -114,6 +118,84 @@
            Methods

            Methods

            + + + + + + + + + + + + + + + + + + + + + + +
            + + + Async + getMultipleTokenPrices + + +
            + + getMultipleTokenPrices(tokenAddresses: string) +
            + Decorators : +
            + @Get('prices/:tokenAddresses')
            @ApiOperation({summary: 'Get prices for multiple tokens'})
            @ApiParam({name: 'tokenAddresses', description: 'Comma-separated list of token addresses', example: '0x0BE9e53fd7EDaC9F859882AfdDa116645287C629,0x6a5F6A8121592BeCd6747a38d67451B310F7f156'})
            @ApiOkResponse({description: 'Success response with prices for multiple tokens', schema: undefined})
            +
            + +
            + +
            + Parameters : + + + + + + + + + + + + + + + + + + + +
            NameTypeOptional
            tokenAddresses + string + + No +
            +
            +
            +
            +
            + Returns : unknown + +
            +
            + +
            +
            @@ -143,8 +225,8 @@

            @@ -311,8 +393,8 @@

            @@ -389,8 +471,8 @@

            @@ -479,8 +561,8 @@

            @@ -587,6 +669,45 @@

            } } + @Get('prices/:tokenAddresses') + @ApiOperation({ + summary: 'Get prices for multiple tokens' + }) + @ApiParam({ + name: 'tokenAddresses', + description: 'Comma-separated list of token addresses', + example: '0x0BE9e53fd7EDaC9F859882AfdDa116645287C629,0x6a5F6A8121592BeCd6747a38d67451B310F7f156' + }) + @ApiOkResponse({ + description: 'Success response with prices for multiple tokens', + schema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + prices: { + type: 'object', + additionalProperties: { + type: 'string', + example: '0.03345197440906984362974767871408538' + } + } + } + } + } + } + }) + async getMultipleTokenPrices ( + @Param('tokenAddresses') tokenAddresses: string + ) { + const prices = await this.tradeApiService.getMultipleTokenPrices(tokenAddresses.split(',')) + + return { + data: { prices } + } + } + @Get('pricechange/:tokenAddress') @ApiOperation({ summary: 'Get price change for token over last 24 hours' diff --git a/documentation/controllers/VoltageDexController.html b/documentation/controllers/VoltageDexController.html index 31c8e123..db796edf 100644 --- a/documentation/controllers/VoltageDexController.html +++ b/documentation/controllers/VoltageDexController.html @@ -83,6 +83,9 @@

            Methods
            @@ -225,9 +249,80 @@

            - + + + + + + + +
            - +
            - +
            - +
            - +
              +
            • + getMultipleTokenPrices +
            • getTokenHistoricalStatistics
            • @@ -115,6 +118,82 @@
              Methods

              Methods

              + + + + + + + + + + + + + + + + + + + + + + +
              + + + getMultipleTokenPrices + + +
              +getMultipleTokenPrices(multipleTokenPricesDto: MultipleTokenPricesDto) +
              + Decorators : +
              + @MessagePattern('get_multiple_token_prices')
              +
              + +
              + +
              + Parameters : + + + + + + + + + + + + + + + + + + + +
              NameTypeOptional
              multipleTokenPricesDto + MultipleTokenPricesDto + + No +
              +
              +
              +
              +
              + Returns : any + +
              +
              + +
              +
              @@ -142,8 +221,8 @@

              @@ -218,8 +297,8 @@

              @@ -294,8 +373,8 @@

              @@ -370,8 +449,8 @@

              @@ -446,8 +525,8 @@

              @@ -506,6 +585,7 @@

              import { TokenPriceDto } from '@app/network-service/voltage-dex/dto/token-price.dto' import { TokenPriceChangeIntervalDto } from '@app/network-service/voltage-dex/dto/token-price-change-interval.dto' import { TokenHistoricalStatisticsDto } from '@app/network-service/voltage-dex/dto/token-stats.dto' +import { MultipleTokenPricesDto } from '@app/network-service/voltage-dex/dto/multiple-token-prices.dto' @Controller('voltage-dex') export class VoltageDexController { @@ -516,6 +596,11 @@

              return this.voltageDexService.getTokenPrice(tokenPriceDto) } + @MessagePattern('get_multiple_token_prices') + getMultipleTokenPrices (@Body() multipleTokenPricesDto: MultipleTokenPricesDto) { + return this.voltageDexService.getMultipleTokenPrices(multipleTokenPricesDto) + } + @MessagePattern('get_token_price_change') getTokenPriceChange (@Body() tokenPriceDto: TokenPriceDto) { return this.voltageDexService.getTokenPriceChange(tokenPriceDto) diff --git a/documentation/coverage.html b/documentation/coverage.html index 1120fccc..dde219e3 100644 --- a/documentation/coverage.html +++ b/documentation/coverage.html @@ -185,6 +185,18 @@ (1/3) +

              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -350,7 +482,43 @@ + + + + + + + + + + + + + + + + + + @@ -365,6 +533,18 @@ (0/1) + + + + + + - + - @@ -410,7 +590,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -662,7 +890,7 @@ @@ -914,7 +1142,7 @@ @@ -1217,6 +1445,18 @@ (0/12) + + + + + + @@ -1274,7 +1514,7 @@ @@ -1298,7 +1538,7 @@ @@ -1877,6 +2117,42 @@ (0/3) + + + + + + + + + + + + + + + + + + + + + + + + @@ -1985,6 +2273,30 @@ (0/1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2402,7 +2762,7 @@ @@ -2426,7 +2786,7 @@ @@ -2477,6 +2837,18 @@ (0/8) + + + + + + @@ -2510,7 +2882,7 @@ @@ -2537,6 +2909,18 @@ (0/1) + + + + + + + + + + + + @@ -2678,7 +3074,7 @@ @@ -2690,7 +3086,19 @@ + + + + + + @@ -2702,7 +3110,7 @@ @@ -2714,7 +3122,7 @@ @@ -2726,7 +3134,7 @@ @@ -2738,7 +3146,7 @@ @@ -2750,7 +3158,7 @@ @@ -3038,7 +3446,7 @@ @@ -3197,18 +3605,6 @@ (0/1) - - - - - - @@ -3662,7 +4058,7 @@ @@ -3866,7 +4262,7 @@ @@ -5165,6 +5561,18 @@ (0/1) + + + + + + @@ -172,8 +172,8 @@

              @@ -227,29 +227,21 @@

              import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
              -import { ApiKeysService } from 'apps/charge-apps-service/src/api-keys/api-keys.service'
              +import { ApiKeysService } from 'apps/charge-api-service/src/api-keys/api-keys.service'
               import * as bcrypt from 'bcryptjs'
              -
               @Injectable()
               export class IsValidApiKeysGuard implements CanActivate {
                 constructor (private apiKeysService: ApiKeysService) { }
               
                 async canActivate (context: ExecutionContext): Promise<boolean> {
              -    const contextType = context.getType()
              -
              -    if (contextType === 'rpc') {
              -      return true
              -    }
              -
                   const request = context.switchToHttp().getRequest()
                   const { query }: { query: { apiKey: string } } = request
              -    const appApiKey = await this.apiKeysService.findOne({ publicKey: query?.apiKey })
              -    const projectSecretHash = appApiKey?.secretHash
              +    const projectApiKey = await this.apiKeysService.findOne({ publicKey: query?.apiKey })
              +    const projectSecretHash = projectApiKey?.secretHash
                   const secretKey = request.header('API-SECRET')
               
              -    request.userId = appApiKey?.ownerId
              -
                   if (projectSecretHash && secretKey) {
              +      request.projectId = projectApiKey.projectId
                     return await bcrypt.compare(secretKey, projectSecretHash)
                   }
                   return false
              diff --git a/documentation/guards/IsValidApiKeysGuard.html b/documentation/guards/IsValidApiKeysGuard.html
              index 6549bb31..e710d1dc 100644
              --- a/documentation/guards/IsValidApiKeysGuard.html
              +++ b/documentation/guards/IsValidApiKeysGuard.html
              @@ -59,7 +59,7 @@
                           

              File

              - apps/charge-api-service/src/api-keys/guards/is-valid-api-keys.guard.ts + apps/charge-apps-service/src/api-keys/guards/is-valid-api-keys.guard.ts

              @@ -107,7 +107,7 @@

              Constructor

              @@ -172,8 +172,8 @@

              @@ -227,21 +227,29 @@

              import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
              -import { ApiKeysService } from 'apps/charge-api-service/src/api-keys/api-keys.service'
              +import { ApiKeysService } from 'apps/charge-apps-service/src/api-keys/api-keys.service'
               import * as bcrypt from 'bcryptjs'
              +
               @Injectable()
               export class IsValidApiKeysGuard implements CanActivate {
                 constructor (private apiKeysService: ApiKeysService) { }
               
                 async canActivate (context: ExecutionContext): Promise<boolean> {
              +    const contextType = context.getType()
              +
              +    if (contextType === 'rpc') {
              +      return true
              +    }
              +
                   const request = context.switchToHttp().getRequest()
                   const { query }: { query: { apiKey: string } } = request
              -    const projectApiKey = await this.apiKeysService.findOne({ publicKey: query?.apiKey })
              -    const projectSecretHash = projectApiKey?.secretHash
              +    const appApiKey = await this.apiKeysService.findOne({ publicKey: query?.apiKey })
              +    const projectSecretHash = appApiKey?.secretHash
                   const secretKey = request.header('API-SECRET')
               
              +    request.userId = appApiKey?.ownerId
              +
                   if (projectSecretHash && secretKey) {
              -      request.projectId = projectApiKey.projectId
                     return await bcrypt.compare(secretKey, projectSecretHash)
                   }
                   return false
              diff --git a/documentation/images/coverage-badge-documentation.svg b/documentation/images/coverage-badge-documentation.svg
              index 20c754dc..fe6ebd64 100644
              --- a/documentation/images/coverage-badge-documentation.svg
              +++ b/documentation/images/coverage-badge-documentation.svg
              @@ -4,6 +4,6 @@
                       
                       
                       documentation
              -        1%
              +        0%
                   
               
              diff --git a/documentation/injectables/ApiKeysService-1.html b/documentation/injectables/ApiKeysService-1.html
              index a8b5d274..df0de9e2 100644
              --- a/documentation/injectables/ApiKeysService-1.html
              +++ b/documentation/injectables/ApiKeysService-1.html
              @@ -57,7 +57,7 @@
                           

              File

              - apps/charge-apps-service/src/api-keys/api-keys.service.ts + apps/charge-api-service/src/api-keys/api-keys.service.ts

              @@ -81,6 +81,10 @@
              Methods
              Async createPublicKey +
            • + Async + createSandboxKey +
            • Async createSecretKey @@ -105,12 +109,20 @@
              Methods
            • Async - getOwnerIdByPublicKey + getProjectIdByPublicKey +
            • +
            • + Async + getProjectJwt
            • Async getPublicKey
            • +
            • + Async + getSandboxKey +
            • Async updateSecretKey @@ -133,12 +145,12 @@

              Constructor

            • @@ -167,6 +179,18 @@

              Constructor

              +
              + + + + + + +
              - +
              - +
              - +
              - +
              - +
              + + apps/charge-accounts-service/src/auth/guards/cron.guard.ts + guardCronGuard + 0 % + (0/2) +
              @@ -257,6 +269,30 @@ (0/4)
              + + apps/charge-accounts-service/src/operators/dto/create-charge-bridge.dto.ts + classCreateChargeBridgeDto + 0 % + (0/3) +
              + + apps/charge-accounts-service/src/operators/dto/create-operator-checkout.dto.ts + classCreateOperatorCheckoutDto + 0 % + (0/4) +
              @@ -317,6 +353,102 @@ (0/4)
              + + apps/charge-accounts-service/src/operators/interfaces/charge-bridge.interface.ts + interfaceChargeBridge + 0 % + (0/12) +
              + + apps/charge-accounts-service/src/operators/interfaces/charge-checkout-webhook-event.interface.ts + interfaceChargeCheckoutWebhookEvent + 0 % + (0/4) +
              + + apps/charge-accounts-service/src/operators/interfaces/operator-checkout.interface.ts + interfaceOperatorCheckout + 0 % + (0/14) +
              + + apps/charge-accounts-service/src/operators/interfaces/operator-invoice.interface.ts + interfaceOperatorInvoice + 0 % + (0/6) +
              + + apps/charge-accounts-service/src/operators/interfaces/operator-project.interface.ts + interfaceOperatorProject + 0 % + (0/11) +
              + + apps/charge-accounts-service/src/operators/interfaces/operator-refresh-token.interface.ts + interfaceOperatorRefreshToken + 0 % + (0/5) +
              + + apps/charge-accounts-service/src/operators/interfaces/operator-user-interface.ts + interfaceOperatorUser + 0 % + (0/9) +
              + + apps/charge-accounts-service/src/operators/interfaces/operator-user-project-response.interface.ts + interfaceOperatorUserProjectResponse + 0 % + (0/3) +
              @@ -326,7 +458,7 @@ OperatorWallet 0 % - (0/4) + (0/5)
              OperatorJwtStrategy 0 % - (0/3) + (0/4) +
              + + apps/charge-accounts-service/src/operators/operators.constants.ts + variablechargeBridgeModelString + 0 % + (0/1) +
              + + apps/charge-accounts-service/src/operators/operators.constants.ts + variableoperatorCheckoutModelString + 0 % + (0/1) +
              + + apps/charge-accounts-service/src/operators/operators.constants.ts + variableoperatorInvoiceModelString + 0 % + (0/1)
              + + apps/charge-accounts-service/src/operators/operators.constants.ts + variableoperatorRefreshTokenModelString + 0 % + (0/1) +
              @@ -377,16 +557,16 @@ (0/1)
              apps/charge-accounts-service/src/operators/operators.controller.ts controller OperatorsController - 70 % - (7/10) + + 85 % + (17/20)
              OperatorsService 0 % - (0/29) + (0/59) +
              + + apps/charge-accounts-service/src/operators/schemas/charge-bridge.schema.ts + variableChargeBridgeSchema + 0 % + (0/1) +
              + + apps/charge-accounts-service/src/operators/schemas/operator-checkout.schema.ts + variableOperatorCheckoutSchema + 0 % + (0/1) +
              + + apps/charge-accounts-service/src/operators/schemas/operator-invoice.schema.ts + variableOperatorInvoiceSchema + 0 % + (0/1) +
              + + apps/charge-accounts-service/src/operators/schemas/operator-refresh-token.schema.ts + variableOperatorRefreshTokenSchema + 0 % + (0/1)
              User 0 % - (0/5) + (0/6)
              BundlerApiInterceptor 0 % - (0/6) + (0/7)
              + + apps/charge-api-service/src/staking-api/staking-api-v2.controller.ts + controllerStakingApiV2Controller + 0 % + (0/5) +
              @@ -1238,7 +1478,7 @@ StakingAPIService 0 % - (0/6) + (0/10)
              TradeApiController 0 % - (0/6) + (0/7)
              TradeApiService 0 % - (0/7) + (0/8)
              + + apps/charge-network-service/src/balances/interfaces/balances.interface.ts + interfaceExplorerServiceCollectibleResponse + 0 % + (0/8) +
              + + apps/charge-network-service/src/balances/interfaces/balances.interface.ts + interfaceExplorerServiceGraphQLVariables + 0 % + (0/6) +
              + + apps/charge-network-service/src/balances/interfaces/balances.interface.ts + interfaceExplorerServiceTransformedCollectible + 0 % + (0/11) +
              @@ -1898,7 +2174,19 @@ UnmarshalService 0 % - (0/12) + (0/18) +
              + + apps/charge-network-service/src/balances/services/unmarshal-balance.service.ts + interfaceNFTMetadata + 0 % + (0/4)
              + + apps/charge-network-service/src/common/constants/graph-queries/masterchef-v3.ts + variableGET_SIMPLE_STAKING_POOL_DATA + 0 % + (0/1) +
              + + apps/charge-network-service/src/common/constants/graph-queries/nfts-v3.ts + variablegetCollectiblesByOwner + 0 % + (0/1) +
              @@ -2033,6 +2345,18 @@ (0/1)
              + + apps/charge-network-service/src/common/constants/graph-queries/voltage-exchange-v2.ts + variablegetMultipleTokenPrices + 0 % + (0/1) +
              @@ -2069,6 +2393,18 @@ (0/1)
              + + apps/charge-network-service/src/common/constants/graph-queries/voltage-exchange-v3.ts + variablegetMultipleTokenUsdPrices + 0 % + (0/1) +
              @@ -2177,6 +2513,18 @@ (0/1)
              + + apps/charge-network-service/src/common/constants/index.ts + variableusdcOnStargateSimpleStakingId + 0 % + (0/1) +
              @@ -2201,6 +2549,18 @@ (0/1)
              + + apps/charge-network-service/src/common/constants/index.ts + variablewethOnStargateSimpleStakingId + 0 % + (0/1) +
              @@ -2234,7 +2594,7 @@ ConsensusService 0 % - (0/39) + (0/38)
              GraphService 0 % - (0/8) + (0/10)
              StakingOption 0 % - (0/4) + (0/5)
              + + apps/charge-network-service/src/staking/staking-providers/simple-staking.service.ts + injectableSimpleStakingService + 0 % + (0/11) +
              @@ -2498,7 +2870,7 @@ StakingController 0 % - (0/5) + (0/9)
              StakingService 0 % - (0/9) + (0/14)
              + + apps/charge-network-service/src/voltage-dex/dto/multiple-token-prices.dto.ts + classMultipleTokenPricesDto + 0 % + (0/2) +
              @@ -2657,6 +3041,18 @@ (0/4)
              + + apps/charge-network-service/src/voltage-dex/services/liquid-staking-fuse-client.service.ts + injectableLiquidStakingFuseClient + 0 % + (0/3) +
              @@ -2666,7 +3062,7 @@ TokenAddressMapper 0 % - (0/2) + (0/4)
              TokenPriceService 0 % - (0/7) + (0/12)
              TokenStatsService 0 % - (0/10) + (0/11) +
              + + apps/charge-network-service/src/voltage-dex/services/volt-bar-client.service.ts + injectableVoltBarClient + 0 % + (0/3)
              VoltageV2Client 0 % - (0/7) + (0/9)
              VoltageV3Client 0 % - (0/6) + (0/7)
              VoltageDexController 0 % - (0/6) + (0/7)
              VoltageDexService 0 % - (0/7) + (0/8)
              BroadcasterService 0 % - (0/9) + (0/13)
              ERC20EventsScannerService 0 % - (0/9) + (0/8)
              - - apps/charge-notifications-service/src/transactions-scanner/interfaces/block-trace.interface.ts - interfaceBlockTrace - 0 % - (0/9) -
              @@ -3254,7 +3650,7 @@ TransactionsScannerService 0 % - (0/9) + (0/8)
              RelayAPIService 0 % - (0/6) + (0/7)
              DataLayerController 0 % - (0/6) + (0/7)
              + + libs/common/src/utils/lowercase_equal.ts + functionisLowercaseEqual + 0 % + (0/1) +
              diff --git a/documentation/dependencies.html b/documentation/dependencies.html index 3e94783b..669767b1 100644 --- a/documentation/dependencies.html +++ b/documentation/dependencies.html @@ -100,6 +100,8 @@ class-transformer : ^0.5.1
            • class-validator : ^0.14.1
            • +
            • + cookie-parser : ^1.4.6
            • crypto-js : ^4.2.0
            • diff --git a/documentation/graph/dependencies.svg b/documentation/graph/dependencies.svg index 53a6e3ff..64a4802b 100644 --- a/documentation/graph/dependencies.svg +++ b/documentation/graph/dependencies.svg @@ -4,2147 +4,2155 @@ - + dependencies - -Legend - -  Declarations - -  Module - -  Bootstrap - -  Providers - -  Exports + +Legend + +  Declarations + +  Module + +  Bootstrap + +  Providers + +  Exports cluster_AccountsModule - + cluster_AccountsModule_imports - + cluster_ApiKeyModule - + cluster_ApiKeyModule_imports - + cluster_ApiKeyModule_exports - + cluster_ApiKeyModule_providers - + cluster_ApiKeysModule - + cluster_AppStoreModule - + cluster_AppStoreModule_providers - + cluster_AuthModule - + cluster_AuthModule_providers - + cluster_BalancesAPIModule - + cluster_BalancesAPIModule_providers - + cluster_BalancesModule - + cluster_BalancesModule_providers - + cluster_BroadcasterModule - + cluster_BroadcasterModule_imports - + cluster_BroadcasterModule_exports - + cluster_BroadcasterModule_providers - + cluster_BundlerApiModule - + cluster_ChargeApiModule - + cluster_ChargeApiModule_exports - + cluster_ChargeApiModule_providers - + cluster_ChargeApiServiceModule - + cluster_ChargeApiServiceModule_imports - + cluster_ChargeApiServiceModule_providers - + cluster_ChargeAppsServiceModule - + cluster_ChargeAppsServiceModule_imports - + cluster_ChargeAppsServiceModule_providers - + cluster_ChargeNetworkServiceModule - + cluster_ChargeNetworkServiceModule_imports - + cluster_ChargeNotificationsServiceModule - + cluster_ChargeNotificationsServiceModule_imports - + cluster_ChargeRelayServiceModule - + cluster_ChargeRelayServiceModule_imports - + cluster_ChargeSmartWalletsServiceModule - + cluster_ChargeSmartWalletsServiceModule_imports - + cluster_ConsensusApiModule - + cluster_ConsensusApiModule_providers - + cluster_ConsensusModule - + cluster_ConsensusModule_exports - + cluster_ConsensusModule_providers - + cluster_DataLayerModule - + cluster_DataLayerModule_providers - + cluster_EthereumPaymentsModule - + cluster_EthereumPaymentsModule_providers - + cluster_EventsScannerModule - - - -cluster_EventsScannerModule_exports - + cluster_EventsScannerModule_providers - + cluster_GraphqlAPIModule - + cluster_GraphqlAPIModule_providers - + cluster_NotificationsModule - + cluster_NotificationsModule_exports - + cluster_NotificationsModule_providers - + cluster_OperatorsModule - + cluster_OperatorsModule_exports - + cluster_OperatorsModule_providers - + cluster_PaymasterApiModule - + cluster_PaymasterApiModule_providers - + cluster_PaymasterModule - + cluster_PaymasterModule_exports - + cluster_PaymasterModule_providers - + cluster_PaymentsModule - + cluster_PaymentsModule_providers - + cluster_ProjectsModule - + cluster_ProjectsModule_exports - + cluster_ProjectsModule_providers - + cluster_RelayAccountsModule - + cluster_RelayAccountsModule_exports - + cluster_RelayAccountsModule_providers - + cluster_SmartWalletsAPIModule - + cluster_SmartWalletsAPIModule_providers - + cluster_SmartWalletsModule - + cluster_SmartWalletsModule_providers - + cluster_StakingAPIModule - + cluster_StakingAPIModule_providers - + cluster_StakingModule - + cluster_StakingModule_exports - + cluster_StakingModule_providers - + cluster_StudioLegacyJwtModule - + cluster_StudioLegacyJwtModule_exports - + cluster_StudioLegacyJwtModule_providers - + cluster_TokenModule - + cluster_TokenModule_exports - + cluster_TokenModule_providers - + cluster_TradeApiModule - + cluster_TradeApiModule_providers - + cluster_TransactionsScannerModule - + cluster_TransactionsScannerModule_providers - + cluster_UsersModule - + cluster_UsersModule_exports - + cluster_UsersModule_providers - + cluster_VoltageDexModule - + cluster_VoltageDexModule_exports - + cluster_VoltageDexModule_providers - + cluster_WebhooksModule - + cluster_WebhooksModule_exports - + cluster_WebhooksModule_providers - + AppStoreModule - -AppStoreModule + +AppStoreModule AccountsModule - -AccountsModule + +AccountsModule AppStoreModule->AccountsModule - - + + AuthModule - -AuthModule + +AuthModule OperatorsModule - -OperatorsModule + +OperatorsModule AuthModule->OperatorsModule - - + + AuthModule->AccountsModule - - + + OperatorsModule->AccountsModule - - + + + + + +BundlerApiModule + +BundlerApiModule + + + +OperatorsModule->BundlerApiModule + + SmartWalletsModule - -SmartWalletsModule + +SmartWalletsModule - + OperatorsModule->SmartWalletsModule - - + + - + OperatorsService - -OperatorsService + +OperatorsService - + OperatorsModule->OperatorsService - - + + PaymasterModule - -PaymasterModule + +PaymasterModule PaymasterModule->OperatorsModule - - + + PaymasterModule->AccountsModule - - + + ApiKeyModule - -ApiKeyModule + +ApiKeyModule PaymasterModule->ApiKeyModule - - + + - + PaymasterService - -PaymasterService + +PaymasterService - + PaymasterModule->PaymasterService - - + + ProjectsModule - -ProjectsModule + +ProjectsModule ProjectsModule->OperatorsModule - - + + - + ProjectsModule->PaymasterModule - - + + ProjectsModule->AccountsModule - - + + ProjectsModule->ApiKeyModule - - + + - + ProjectsModule->SmartWalletsModule - - + + - + ProjectsService - -ProjectsService + +ProjectsService - + ProjectsModule->ProjectsService - - + + UsersModule - -UsersModule + +UsersModule UsersModule->AppStoreModule - - + + UsersModule->AuthModule - - + + - + UsersModule->OperatorsModule - - + + - + UsersModule->PaymasterModule - - + + - + UsersModule->ProjectsModule - - + + UsersModule->AccountsModule - - + + - + UsersModule->SmartWalletsModule - - + + UsersService - -UsersService + +UsersService - + UsersModule->UsersService - - + + DatabaseModule - -DatabaseModule + +DatabaseModule DatabaseModule->AppStoreModule - - + + DatabaseModule->OperatorsModule - - + + - + DatabaseModule->PaymasterModule - - + + - + DatabaseModule->ProjectsModule - - + + - + DatabaseModule->UsersModule - - + + DatabaseModule->ApiKeyModule - - + + ApiKeysModule - -ApiKeysModule + +ApiKeysModule DatabaseModule->ApiKeysModule - - + + WebhooksModule - -WebhooksModule + +WebhooksModule - + DatabaseModule->WebhooksModule - - + + BroadcasterModule - -BroadcasterModule + +BroadcasterModule DatabaseModule->BroadcasterModule - - + + ChargeApiModule - -ChargeApiModule + +ChargeApiModule - + DatabaseModule->ChargeApiModule - - + + EthereumPaymentsModule - -EthereumPaymentsModule + +EthereumPaymentsModule - + DatabaseModule->EthereumPaymentsModule - - + + PaymentsModule - -PaymentsModule + +PaymentsModule - + DatabaseModule->PaymentsModule - - + + EventsScannerModule - -EventsScannerModule + +EventsScannerModule - + DatabaseModule->EventsScannerModule - - + + TransactionsScannerModule - -TransactionsScannerModule + +TransactionsScannerModule - + DatabaseModule->TransactionsScannerModule - - + + RelayAccountsModule - -RelayAccountsModule + +RelayAccountsModule - + DatabaseModule->RelayAccountsModule - - + + DataLayerModule - -DataLayerModule + +DataLayerModule - + DatabaseModule->DataLayerModule - - + + - + DatabaseModule->SmartWalletsModule - - + + StudioLegacyJwtModule - -StudioLegacyJwtModule + +StudioLegacyJwtModule StudioLegacyJwtModule->ApiKeyModule - - + + StudioLegacyJwtService - -StudioLegacyJwtService + +StudioLegacyJwtService - + StudioLegacyJwtModule->StudioLegacyJwtService - - + + ApiKeyModule->OperatorsModule - - + + ApiKeysService - -ApiKeysService + +ApiKeysService ApiKeyModule->ApiKeysService - - + + BalancesAPIModule - -BalancesAPIModule + +BalancesAPIModule ApiKeyModule->BalancesAPIModule - - - - - -BundlerApiModule - -BundlerApiModule + + ApiKeyModule->BundlerApiModule - - + + ExplorerApiModule - -ExplorerApiModule + +ExplorerApiModule ApiKeyModule->ExplorerApiModule - - + + GraphqlAPIModule - -GraphqlAPIModule + +GraphqlAPIModule ApiKeyModule->GraphqlAPIModule - - + + LegacyApiModule - -LegacyApiModule + +LegacyApiModule ApiKeyModule->LegacyApiModule - - + + NotificationsModule - -NotificationsModule + +NotificationsModule ApiKeyModule->NotificationsModule - - + + PaymasterApiModule - -PaymasterApiModule + +PaymasterApiModule - + ApiKeyModule->PaymasterApiModule - - + + SmartWalletsAPIModule - -SmartWalletsAPIModule + +SmartWalletsAPIModule - + ApiKeyModule->SmartWalletsAPIModule - - + + StakingAPIModule - -StakingAPIModule + +StakingAPIModule - + ApiKeyModule->StakingAPIModule - - + + TradeApiModule - -TradeApiModule + +TradeApiModule - + ApiKeyModule->TradeApiModule - - + + ChargeApiServiceModule - -ChargeApiServiceModule + +ChargeApiServiceModule - + ApiKeyModule->ChargeApiServiceModule - - + + ApiKeysService - -ApiKeysService + +ApiKeysService ApiKeysService->ApiKeyModule - - + + ApiKeysService->ApiKeysModule - - + + ApiKeysModule->ApiKeysService - - + + - + ApiKeysModule->EthereumPaymentsModule - - + + - + ApiKeysModule->PaymentsModule - - + + ChargeAppsServiceModule - -ChargeAppsServiceModule + +ChargeAppsServiceModule - + ApiKeysModule->ChargeAppsServiceModule - - + + AppStoreService - -AppStoreService + +AppStoreService AppStoreService->AppStoreModule - - + + JwtStrategy - -JwtStrategy + +JwtStrategy JwtStrategy->AuthModule - - + + - + JwtStrategy->SmartWalletsAPIModule - - + + - + BalancesAPIModule->ChargeApiServiceModule - - + + BalancesAPIService - -BalancesAPIService + +BalancesAPIService BalancesAPIService->BalancesAPIModule - - + + BalancesService - -BalancesService + +BalancesService BalancesModule - -BalancesModule + +BalancesModule BalancesService->BalancesModule - - + + ExplorerService - -ExplorerService + +ExplorerService ExplorerService->BalancesModule - - + + GraphQLService - -GraphQLService + +GraphQLService GraphQLService->BalancesModule - - + + GraphqlModule - -GraphqlModule + +GraphqlModule GraphQLService->GraphqlModule - - + + TokenService - -TokenService + +TokenService TokenService->BalancesModule - - + + - + TokenService->DataLayerModule - - + + UnmarshalService - -UnmarshalService + +UnmarshalService UnmarshalService->BalancesModule - - + + ChargeNetworkServiceModule - -ChargeNetworkServiceModule + +ChargeNetworkServiceModule - + BalancesModule->ChargeNetworkServiceModule - - + + WebhooksModule->BroadcasterModule - - + + - + WebhooksModule->EventsScannerModule - - + + - + WebhooksModule->TransactionsScannerModule - - + + ChargeNotificationsServiceModule - -ChargeNotificationsServiceModule + +ChargeNotificationsServiceModule - + WebhooksModule->ChargeNotificationsServiceModule - - + + WebhooksService - -WebhooksService + +WebhooksService - + WebhooksModule->WebhooksService - - + + BroadcasterService - -BroadcasterService + +BroadcasterService BroadcasterModule->BroadcasterService - - + + - + BroadcasterModule->EventsScannerModule - - + + - + BroadcasterModule->TransactionsScannerModule - - + + - + BroadcasterModule->ChargeNotificationsServiceModule - - + + BroadcasterService - -BroadcasterService + +BroadcasterService BroadcasterService->BroadcasterModule - - + + WebhookSendService - -WebhookSendService + +WebhookSendService WebhookSendService->BroadcasterModule - - + + - + WebhookSendService->EthereumPaymentsModule - - + + - + WebhookSendService->PaymentsModule - - + + - + BundlerApiModule->ChargeApiServiceModule - - + + ChargeApiService - -ChargeApiService + +ChargeApiService - + ChargeApiModule->ChargeApiService - - + + - + ChargeApiModule->PaymentsModule - - + + - + ChargeApiModule->SmartWalletsModule - - + + ChargeApiService - -ChargeApiService + +ChargeApiService - + ChargeApiService->ChargeApiModule - - + + ConsensusApiModule - -ConsensusApiModule + +ConsensusApiModule - + ConsensusApiModule->ChargeApiServiceModule - - + + - + ExplorerApiModule->ChargeApiServiceModule - - + + - + GraphqlAPIModule->ChargeApiServiceModule - - + + - + LegacyApiModule->ChargeApiServiceModule - - + + - + NotificationsModule->ChargeApiServiceModule - - + + - + NotificationsModule->SmartWalletsModule - - + + - + NotificationsService - -NotificationsService + +NotificationsService NotificationsModule->NotificationsService - - + + - + PaymasterApiModule->ChargeApiServiceModule - - + + - + SmartWalletsAPIModule->ChargeApiServiceModule - - + + - + SmartWalletsAPIModule->ChargeNotificationsServiceModule - - + + - + StakingAPIModule->ChargeApiServiceModule - - + + - + TradeApiModule->ChargeApiServiceModule - - + + ChargeApiServiceService - -ChargeApiServiceService + +ChargeApiServiceService - + ChargeApiServiceService->ChargeApiServiceModule - - + + - + EthereumPaymentsModule->ChargeAppsServiceModule - - + + - + PaymentsModule->ChargeAppsServiceModule - - + + ChargeAppsServiceService - -ChargeAppsServiceService + +ChargeAppsServiceService - + ChargeAppsServiceService->ChargeAppsServiceModule - - + + ConsensusModule - -ConsensusModule + +ConsensusModule - + ConsensusModule->ChargeNetworkServiceModule - - + + ConsensusService - -ConsensusService + +ConsensusService - + ConsensusModule->ConsensusService - - + + - + GraphqlModule->ChargeNetworkServiceModule - - + + StakingModule - -StakingModule + +StakingModule - + StakingModule->ChargeNetworkServiceModule - - + + - + StakingService - -StakingService + +StakingService - + StakingModule->StakingService - - + + TokenModule - -TokenModule + +TokenModule + + + +TokenModule->OperatorsModule + + - + TokenModule->StakingModule - - + + - + TokenModule->ChargeNetworkServiceModule - - + + - + TokenModule->DataLayerModule - - + + - + TokenModule->SmartWalletsModule - - + + ChargeSmartWalletsServiceModule - -ChargeSmartWalletsServiceModule + +ChargeSmartWalletsServiceModule - + TokenModule->ChargeSmartWalletsServiceModule - - + + TradeService - -TradeService + +TradeService - + TokenModule->TradeService - - + + VoltageDexModule - -VoltageDexModule + +VoltageDexModule - + VoltageDexModule->ChargeNetworkServiceModule - - + + VoltageDexService - -VoltageDexService + +VoltageDexService - + VoltageDexModule->VoltageDexService - - + + - + EventsScannerModule->ChargeNotificationsServiceModule - - - - - -EventsScannerService - -EventsScannerService - - - -EventsScannerModule->EventsScannerService - - + + - + TransactionsScannerModule->ChargeNotificationsServiceModule - - + + ChargeRelayServiceModule - -ChargeRelayServiceModule + +ChargeRelayServiceModule - + RelayAccountsModule->ChargeRelayServiceModule - - + + - + RelayAccountsService - -RelayAccountsService + +RelayAccountsService - + RelayAccountsModule->RelayAccountsService - - + + - + DataLayerModule->ChargeSmartWalletsServiceModule - - + + - + SmartWalletsModule->ChargeSmartWalletsServiceModule - - + + ConsensusApiService - -ConsensusApiService + +ConsensusApiService - + ConsensusApiService->ConsensusApiModule - - + + ConsensusService - -ConsensusService + +ConsensusService - + ConsensusService->ConsensusModule - - + + AnalyticsService - -AnalyticsService + +AnalyticsService - + AnalyticsService->OperatorsModule - - + + - + AnalyticsService->DataLayerModule - - + + - + AnalyticsService->SmartWalletsModule - - + + DataLayerService - -DataLayerService + +DataLayerService - + DataLayerService->DataLayerModule - - + + SmartWalletsAAEventsService - -SmartWalletsAAEventsService + +SmartWalletsAAEventsService - + SmartWalletsAAEventsService->DataLayerModule - - + + UserOpFactory - -UserOpFactory + +UserOpFactory - + UserOpFactory->DataLayerModule - - + + UserOpParser - -UserOpParser + +UserOpParser - + UserOpParser->PaymasterApiModule - - + + - + UserOpParser->DataLayerModule - - + + BackendWalletsEthereumService - -BackendWalletsEthereumService + +BackendWalletsEthereumService - + BackendWalletsEthereumService->EthereumPaymentsModule - - + + EthereumPaymentsService - -EthereumPaymentsService + +EthereumPaymentsService - + EthereumPaymentsService->EthereumPaymentsModule - - + + - + ERC20EventsScannerService - -ERC20EventsScannerService + +ERC20EventsScannerService ERC20EventsScannerService->EventsScannerModule - - + + - + GasService - -GasService + +GasService GasService->EventsScannerModule - - + + - + GasService->TransactionsScannerModule - - + + - + UserOpEventsScannerService - -UserOpEventsScannerService + +UserOpEventsScannerService UserOpEventsScannerService->EventsScannerModule - - + + - + GraphqlAPIService - -GraphqlAPIService + +GraphqlAPIService GraphqlAPIService->GraphqlAPIModule - - + + - + NotificationsService - -NotificationsService + +NotificationsService NotificationsService->NotificationsModule - - + + - + OperatorJwtStrategy - -OperatorJwtStrategy + +OperatorJwtStrategy - + OperatorJwtStrategy->OperatorsModule - - + + - + OperatorsService - -OperatorsService + +OperatorsService - + OperatorsService->OperatorsModule - - + + - + PaymasterApiService - -PaymasterApiService + +PaymasterApiService - + PaymasterApiService->PaymasterApiModule - - + + - + PaymasterService - -PaymasterService + +PaymasterService - + PaymasterService->PaymasterModule - - + + - + PaymentsService - -PaymentsService + +PaymentsService - + PaymentsService->PaymentsModule - - + + - + ProjectsService - -ProjectsService + +ProjectsService - + ProjectsService->ProjectsModule - - + + - + RelayAccountsService - -RelayAccountsService + +RelayAccountsService - + RelayAccountsService->RelayAccountsModule - - + + - + SmartWalletsAPIService - -SmartWalletsAPIService + +SmartWalletsAPIService - + SmartWalletsAPIService->SmartWalletsAPIModule - - + + - + RelayAPIService - -RelayAPIService + +RelayAPIService - + RelayAPIService->SmartWalletsModule - - + + - + SmartWalletsAAService - -SmartWalletsAAService + +SmartWalletsAAService - + SmartWalletsAAService->SmartWalletsModule - - + + - + SmartWalletsEventsService - -SmartWalletsEventsService + +SmartWalletsEventsService - + SmartWalletsEventsService->SmartWalletsModule - - + + - + SmartWalletsLegacyService - -SmartWalletsLegacyService + +SmartWalletsLegacyService - + SmartWalletsLegacyService->SmartWalletsModule - - + + - + StakingAPIService - -StakingAPIService + +StakingAPIService - + StakingAPIService->StakingAPIModule - - + + - + FuseLiquidStakingService - -FuseLiquidStakingService + +FuseLiquidStakingService - + FuseLiquidStakingService->StakingModule - - + + - + GraphService - -GraphService + +GraphService - + GraphService->StakingModule - - + + + + + +SimpleStakingService + +SimpleStakingService + + + +SimpleStakingService->StakingModule + + StakingService - -StakingService + +StakingService - + StakingService->StakingModule - - + + VoltBarService - -VoltBarService + +VoltBarService - + VoltBarService->StakingModule - - + + StudioLegacyJwtService - -StudioLegacyJwtService + +StudioLegacyJwtService - + StudioLegacyJwtService->StudioLegacyJwtModule - - + + TradeService - -TradeService + +TradeService - + TradeService->TokenModule - - + + TradeApiService - -TradeApiService + +TradeApiService - + TradeApiService->TradeApiModule - - + + TransactionsScannerService - -TransactionsScannerService + +TransactionsScannerService - + TransactionsScannerService->TransactionsScannerModule - - + + UsersService - -UsersService + +UsersService - + UsersService->UsersModule - - + + TokenAddressMapper - -TokenAddressMapper + +TokenAddressMapper - + TokenAddressMapper->VoltageDexModule - - + + TokenPriceService - -TokenPriceService + +TokenPriceService - + TokenPriceService->VoltageDexModule - - + + TokenStatsService - -TokenStatsService + +TokenStatsService - + TokenStatsService->VoltageDexModule - - + + VoltageDexService - -VoltageDexService + +VoltageDexService - + VoltageDexService->VoltageDexModule - - + + WebhooksService - -WebhooksService + +WebhooksService - + WebhooksService->WebhooksModule - - + + diff --git a/documentation/guards/CronGuard.html b/documentation/guards/CronGuard.html new file mode 100644 index 00000000..33fbcff0 --- /dev/null +++ b/documentation/guards/CronGuard.html @@ -0,0 +1,264 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
              +
              + + +
              +
              + + + + + + + + + + + + + +
              +
              +

              +

              File

              +

              +

              + apps/charge-accounts-service/src/auth/guards/cron.guard.ts +

              + + + + + + +
              +

              Index

              + + + + + + + + + + + + + + + +
              +
              Methods
              +
              + +
              +
              + + +
              + +

              + Methods +

              + + + + + + + + + + + + + + + + + + + +
              + + + Async + canActivate + + +
              + + canActivate(context: ExecutionContext) +
              + +
              + +
              + Parameters : + + + + + + + + + + + + + + + + + + + +
              NameTypeOptional
              context + ExecutionContext + + No +
              +
              +
              +
              +
              + Returns : Promise<boolean> + +
              +
              + +
              +
              +
              +
              + + +
              +
              import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
              +
              +@Injectable()
              +export class CronGuard implements CanActivate {
              +  async canActivate (context: ExecutionContext): Promise<boolean> {
              +    const request = context.switchToHttp().getRequest()
              +    const headers = request.headers
              +    const cronHeader = headers['x-cron-secret']
              +    if (!cronHeader) return false
              +    return cronHeader === process.env.CRON_JOB_SECRET
              +  }
              +}
              +
              +
              +
              + + + + + + + + + + +
              +
              +

              results matching ""

              +
                +
                +
                +

                No results matching ""

                +
                +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/guards/IsValidApiKeysGuard-1.html b/documentation/guards/IsValidApiKeysGuard-1.html index 2f16d2e3..17396a27 100644 --- a/documentation/guards/IsValidApiKeysGuard-1.html +++ b/documentation/guards/IsValidApiKeysGuard-1.html @@ -59,7 +59,7 @@

                File

                - apps/charge-apps-service/src/api-keys/guards/is-valid-api-keys.guard.ts + apps/charge-api-service/src/api-keys/guards/is-valid-api-keys.guard.ts

                @@ -107,7 +107,7 @@

                Constructor

              • - +
                - +
                - +
                - +
                -constructor(apiKeyModel: Model) +constructor(apiKeyModel: Model, studioLegacyJwtService: StudioLegacyJwtService)
                - +
                studioLegacyJwtService + StudioLegacyJwtService + + No +
                @@ -196,15 +220,15 @@

              - createPublicKey(apiKeysDto: ApiKeysDto) + createPublicKey(projectId: string)
              - +
              apiKeysDtoprojectId - ApiKeysDto + string + + No +
              + +
              +
              +
              + Returns : unknown + +
              +
              + +
              +
            + + + + + + + + + + + + + + + + + @@ -249,80 +225,9 @@

            - + - - - - - - -
            + + + Async + createSandboxKey + + +
            + + createSandboxKey(projectId: string) +
            + +
            + +
            + Parameters : + + + + + + + + + + + + + @@ -296,9 +391,21 @@

            - + + + + + + + + + @@ -417,8 +524,8 @@

            @@ -457,8 +564,8 @@

            @@ -489,15 +596,15 @@

            @@ -518,9 +625,9 @@

            - + @@ -616,6 +723,77 @@

            NameTypeOptional
            projectId + string @@ -267,15 +362,15 @@

            - createSecretKey(apiKeysDto: ApiKeysDto) + createSecretKey(projectId: string, createLegacyAccount: boolean)
            - +
            apiKeysDtoprojectId + string + + No +
            createLegacyAccount - ApiKeysDto + boolean @@ -345,8 +452,8 @@

            - +
            - +
            - +
            - getApiKeysInfo(apiKeysDto: ApiKeysDto) + getApiKeysInfo(projectId: string)
            - +
            apiKeysDtoprojectId - ApiKeysDto + string @@ -549,26 +656,26 @@

            - + Async - getOwnerIdByPublicKey - + getProjectIdByPublicKey +
            - getOwnerIdByPublicKey(publicKey: any) + getProjectIdByPublicKey(publicKey: any)
            - +
            + + + + + + + + + + + + + + + + + + + +
            + + + Async + getProjectJwt + + +
            + + getProjectJwt(query: object) +
            + +
            + +
            + Parameters : + + + + + + + + + + + + + + + + + + + +
            NameTypeOptional
            query + object + + No +
            +
            +
            +
            +
            + Returns : unknown + +
            +
            + +
            +
            @@ -631,15 +809,86 @@

            + + + + + + + + + + + + +
            - getPublicKey(apiKeysDto: ApiKeysDto) + getPublicKey(projectId: string) +
            + +
            + +
            + Parameters : + + + + + + + + + + + + + + + + + + + +
            NameTypeOptional
            projectId + string + + No +
            +
            +
            +
            +
            + Returns : unknown + +
            +
            + +
            +
            + + + + + + + @@ -660,9 +909,9 @@

            - + @@ -731,9 +980,9 @@

            - + @@ -179,18 +167,6 @@

            Constructor

            - - - - - - - -
            + + + Async + getSandboxKey + + +
            + + getSandboxKey(projectId: string)
            - +
            apiKeysDtoprojectId - ApiKeysDto + string @@ -702,15 +951,15 @@

            - updateSecretKey(apiKeysDto: ApiKeysDto) + updateSecretKey(projectId: string)
            - +
            apiKeysDtoprojectId - ApiKeysDto + string @@ -764,40 +1013,38 @@

            -
            import { ApiKey } from '@app/apps-service/api-keys/interfaces/api-keys.interface '
            -import { apiKeyModelString } from '@app/apps-service/api-keys/api-keys.constants'
            -import { Inject, Injectable } from '@nestjs/common'
            +        
            import { Inject, Injectable } from '@nestjs/common'
             import { Model } from 'mongoose'
            -import { RpcException } from '@nestjs/microservices'
            -import { isEmpty } from 'lodash'
            +import { ApiKey } from 'apps/charge-api-service/src/api-keys/interfaces/api-keys.interface '
            +import { apiKeyModelString } from 'apps/charge-api-service/src/api-keys/api-keys.constants'
             import * as bcrypt from 'bcryptjs'
             import * as crypto from 'crypto'
             import base64url from 'base64url'
            -import { ApiKeysDto } from '@app/apps-service/api-keys/dto/api-keys.dto'
            +import { RpcException } from '@nestjs/microservices'
            +import { StudioLegacyJwtService } from '@app/api-service/studio-legacy-jwt/studio-legacy-jwt.service'
            +import { isEmpty } from 'lodash'
             
             @Injectable()
             export class ApiKeysService {
               constructor (
            -        @Inject(apiKeyModelString)
            -        private apiKeyModel: Model<ApiKey>
            +    @Inject(apiKeyModelString)
            +    private apiKeyModel: Model<ApiKey>,
            +    private studioLegacyJwtService: StudioLegacyJwtService
               ) { }
             
            -  async createPublicKey (apiKeysDto: ApiKeysDto) {
            -    const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName]
            -
            -    const appKeys = await this.apiKeyModel.findOne({
            -      ownerId, appName
            +  async createPublicKey (projectId: string) {
            +    const projectKeys = await this.apiKeyModel.findOne({
            +      projectId
                 })
             
            -    if (appKeys) {
            +    if (projectKeys) {
                   throw new RpcException('Public Keys already exist')
                 }
             
                 const publicKey = `pk_${await this.generateRandomToken()}`
             
                 const result = await this.apiKeyModel.create({
            -      ownerId,
            -      appName,
            +      projectId,
                   publicKey
                 })
             
            @@ -810,10 +1057,8 @@ 

            throw new RpcException('Internal Server Error') } - async getPublicKey (apiKeysDto: ApiKeysDto) { - const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] - - const apiKeys = await this.findOne({ ownerId, appName }) + async getPublicKey (projectId: string) { + const apiKeys = await this.findOne({ projectId }) if (apiKeys && apiKeys?.publicKey) { return { publicKey: apiKeys?.publicKey } @@ -826,10 +1071,8 @@

            return this.apiKeyModel.findOne(query) } - async createSecretKey (apiKeysDto: ApiKeysDto) { - const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] - - const apiKeys = await this.apiKeyModel.findOne({ ownerId, appName }) + async createSecretKey (projectId: string, createLegacyAccount: boolean) { + const apiKeys = await this.apiKeyModel.findOne({ projectId }) if (apiKeys && apiKeys?.secretHash) { throw new RpcException('Secret Key already exists') @@ -839,35 +1082,94 @@

            const saltRounds = await bcrypt.genSalt() const secretHash = await bcrypt.hash(secretKey, saltRounds) + if (createLegacyAccount) { + const { encryptedLegacyJwt, legacyBackendAccount } = await this.studioLegacyJwtService.createLegacyJwt(`chargeApp_${projectId}`) + const result = await this.apiKeyModel.findOneAndUpdate( + { projectId }, + { + secretHash, + secretPrefix, + secretLastFourChars, + encryptedLegacyJwt, + legacyBackendAccount + }, + { upsert: true, new: true } + ) + if (result) { + return { + secretKey + } + } + } else { + const result = await this.apiKeyModel.findOneAndUpdate( + { projectId }, + { + secretHash, + secretPrefix, + secretLastFourChars + }, + { upsert: true, new: true } + ) + if (result) { + return { + secretKey + } + } + } + throw new RpcException('Internal Server Error') + } + + async createSandboxKey (projectId: string) { + const projectKeys = await this.apiKeyModel.findOne({ + projectId + }) + + if (projectKeys && projectKeys?.sandboxKey) { + throw new RpcException('sandboxKey Key already exist') + } + const sandboxKey = `pk_test_${await this.generateRandomToken()}` + const result = await this.apiKeyModel.findOneAndUpdate( - { ownerId, appName }, + { projectId }, { - secretHash, - secretPrefix, - secretLastFourChars - }, - { upsert: true, new: true } + projectId, + sandboxKey + } ) if (result) { return { - secretKey + sandboxKey } } throw new RpcException('Internal Server Error') } - async updateSecretKey (apiKeysDto: ApiKeysDto) { - const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] + async getSandboxKey (projectId: string) { + const apiKeys = await this.findOne({ projectId }) + if (apiKeys && apiKeys?.sandboxKey) { + return { sandboxKey: apiKeys?.sandboxKey } + } + + throw new RpcException('Not Found') + } + + async getProjectJwt (query: object) { + const projectApiKeys = await this.apiKeyModel.findOne(query) + const projectEncryptedJwt = projectApiKeys?.encryptedLegacyJwt + return this.studioLegacyJwtService.decryptEncryptedJWT(projectEncryptedJwt) + } + + async updateSecretKey (projectId: string) { const { secretKey, secretPrefix, secretLastFourChars } = await this.generateSecretKey() const saltRounds = await bcrypt.genSalt() const secretHash = await bcrypt.hash(secretKey, saltRounds) const result = await this.apiKeyModel.findOneAndUpdate( { - ownerId, appName + projectId }, { secretHash, @@ -884,22 +1186,20 @@

            } } - async getOwnerIdByPublicKey (publicKey: any) { + async getProjectIdByPublicKey (publicKey: any) { const projectApiKeys: ApiKey | null = await this.apiKeyModel.findOne({ publicKey }) - const ownerId: string = projectApiKeys?.ownerId?.toString() + const projectId: string = projectApiKeys?.projectId?.toString() - if (isEmpty(ownerId)) { + if (isEmpty(projectId)) { return new Error('Project not found') } - return ownerId + return projectId } - async getApiKeysInfo (apiKeysDto: ApiKeysDto) { - const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] - + async getApiKeysInfo (projectId: string) { const projectApiKeys = await this.apiKeyModel.findOne({ - ownerId, appName + projectId }) .select('-secretHash -encryptedLegacyJwt') diff --git a/documentation/injectables/ApiKeysService.html b/documentation/injectables/ApiKeysService.html index f0cf6428..fb6336a7 100644 --- a/documentation/injectables/ApiKeysService.html +++ b/documentation/injectables/ApiKeysService.html @@ -57,7 +57,7 @@

            File

            - apps/charge-api-service/src/api-keys/api-keys.service.ts + apps/charge-apps-service/src/api-keys/api-keys.service.ts

            @@ -81,10 +81,6 @@
            Methods
            Async createPublicKey -
          • - Async - createSandboxKey -
          • Async createSecretKey @@ -109,20 +105,12 @@
            Methods
          • Async - getProjectIdByPublicKey -
          • -
          • - Async - getProjectJwt + getOwnerIdByPublicKey
          • Async getPublicKey
          • -
          • - Async - getSandboxKey -
          • Async updateSecretKey @@ -145,12 +133,12 @@

            Constructor

          • -constructor(apiKeyModel: Model, studioLegacyJwtService: StudioLegacyJwtService) +constructor(apiKeyModel: Model)
            - +
            studioLegacyJwtService - StudioLegacyJwtService - - No -
            @@ -220,15 +196,15 @@

            - createPublicKey(projectId: string) + createPublicKey(apiKeysDto: ApiKeysDto)
            - +
            projectIdapiKeysDto - string - - No -
            -

            -
            -
            -
            - Returns : unknown - -
            -
            - -
            - - - - - - - - - - - - - - - - - - - - - @@ -681,7 +830,7 @@

            @@ -704,6 +853,9 @@

            @Injectable() export class BroadcasterService { private readonly logger = new Logger(BroadcasterService.name) + private isProcessing = false + private readonly MAX_RETRY_ATTEMPTS = 6 + private readonly PROCESSING_INTERVAL_MS = 2000 constructor ( @Inject(webhookEventModelString) @@ -729,60 +881,99 @@

            } async start () { - while (true) { - const webhookEventsToSendNow = await this.webhookEventModel.find( - { - retryAfter: { $lte: new Date() }, - success: false, - numberOfTries: { $lt: 6 } + if (this.isProcessing) { + return + } + + this.isProcessing = true + + try { + while (true) { + await this.processWebhookQueue() + // Add delay between iterations to prevent excessive CPU usage + await new Promise(resolve => setTimeout(resolve, this.PROCESSING_INTERVAL_MS)) + } + } catch (error) { + this.isProcessing = false + this.logger.error(`Failed to process webhook queue: ${error}`) + // Restart after delay if there was an error + setTimeout(() => this.start(), this.PROCESSING_INTERVAL_MS) + } + } + + async processWebhookQueue () { + const webhookEventsToSendNow = await this.webhookEventModel.find( + { + retryAfter: { $lte: new Date() }, + success: false, + numberOfTries: { $lt: this.MAX_RETRY_ATTEMPTS } + } + ).populate<{ webhook: Webhook }>('webhook').sort({ retryAfter: -1 }).limit(100) + + this.logger.log(`Processing ${webhookEventsToSendNow.length} webhook events`) + + for (const webhookEvent of webhookEventsToSendNow) { + try { + const updatedEvent = await this.webhookEventModel.findByIdAndUpdate( + webhookEvent._id, + { $inc: { numberOfTries: 1 } }, + { new: true } + ) + + if (!updatedEvent) { + this.logger.warn(`Webhook event ${webhookEvent._id} no longer exists`) + continue } - ).populate<{ webhook: Webhook }>('webhook').sort({ retryAfter: -1 }) - for (const webhookEvent of webhookEventsToSendNow) { - try { - this.logger.log(`Starting sending to ${webhookEvent.webhook.webhookUrl}. TxHash: ${webhookEvent.eventData.txHash}`) - webhookEvent.numberOfTries++ - const response = await this.webhookSendService.sendData(webhookEvent) - webhookEvent.responses.push(this.getResponseDetailsWithDate(response.status, response.statusText)) - webhookEvent.success = true - } catch (err) { - let errorStatus: number, errorResponse: string - if (err instanceof HttpException) { - errorStatus = err.getStatus() - errorResponse = err.getResponse().toString() - if (isNaN(errorStatus)) { - this.logger.warn(`Webhook ${webhookEvent._id} unable to send an webhook event to its URL:${webhookEvent.webhook.webhookUrl}` - ) - } else { - // this.logger.error( - // `Webhook ${webhookEvent._id} returned error. `, - // `Error message: ${errorResponse}`, - // `Error status: ${errorStatus}` - // ) - } + webhookEvent.numberOfTries = updatedEvent.numberOfTries + + this.logger.log(`Sending to ${webhookEvent.webhook.webhookUrl}. TxHash: ${webhookEvent.eventData.txHash}, Attempt: ${webhookEvent.numberOfTries}`) + + const response = await this.webhookSendService.sendData(webhookEvent) + + await this.webhookEventModel.findByIdAndUpdate( + webhookEvent._id, + { + $set: { success: true }, + $push: { responses: this.getResponseDetailsWithDate(response.status, response.statusText) } + } + ) + } catch (err) { + let errorStatus: number, errorResponse: string + if (err instanceof HttpException) { + errorStatus = err.getStatus() + errorResponse = err.getResponse().toString() + if (isNaN(errorStatus)) { + this.logger.warn(`Webhook ${webhookEvent._id} unable to send an webhook event to its URL:${webhookEvent.webhook.webhookUrl}`) } else { - errorStatus = HttpStatus.INTERNAL_SERVER_ERROR - errorResponse = JSON.stringify(err) - // this.logger.error( - // `Webhook ${webhookEvent._id} returned error. `, - // `Error message: ${errorResponse}`, - // `Error status: ${errorStatus}` - // ) + this.logger.error( + `Webhook ${webhookEvent._id} returned error. `, + `Error message: ${errorResponse}`, + `Error status: ${errorStatus}` + ) } - - webhookEvent.responses.push( - this.getResponseDetailsWithDate(errorStatus, errorResponse) + } else { + errorStatus = HttpStatus.INTERNAL_SERVER_ERROR + errorResponse = JSON.stringify(err) + this.logger.error( + `Webhook ${webhookEvent._id} returned error. `, + `Error message: ${errorResponse}`, + `Error status: ${errorStatus}` ) + } + + const retryAfter = new Date(this.getNewRetryAfterDate(webhookEvent)) - webhookEvent.retryAfter = new Date( - this.getNewRetryAfterDate(webhookEvent) + try { + await this.webhookEventModel.findByIdAndUpdate( + webhookEvent._id, + { + $push: { responses: this.getResponseDetailsWithDate(errorStatus, errorResponse) }, + $set: { retryAfter } + } ) - } finally { - try { - await webhookEvent.save() - } catch (err) { - this.logger.error(`Failed to save webhookEvent ${webhookEvent._id}: ${err}`) - } + } catch (err) { + this.logger.error(`Failed to save webhookEvent ${webhookEvent._id}: ${err}`) } } } diff --git a/documentation/injectables/BundlerApiInterceptor.html b/documentation/injectables/BundlerApiInterceptor.html index b808120a..7e424908 100644 --- a/documentation/injectables/BundlerApiInterceptor.html +++ b/documentation/injectables/BundlerApiInterceptor.html @@ -93,6 +93,10 @@

            Methods
            @@ -182,6 +186,18 @@

            Constructor

            + + + + + + + +
            - - - Async - createSandboxKey - - -
            - - createSandboxKey(projectId: string) -
            - -
            - -
            - Parameters : - - - - - - - - - - - - - @@ -391,21 +296,9 @@

            - - - - - - - - - + @@ -524,8 +417,8 @@

            @@ -564,8 +457,8 @@

            @@ -596,15 +489,15 @@

            @@ -625,9 +518,9 @@

            - + @@ -723,77 +616,6 @@

            NameTypeOptional
            projectId - string + ApiKeysDto @@ -362,15 +267,15 @@

            - createSecretKey(projectId: string, createLegacyAccount: boolean) + createSecretKey(apiKeysDto: ApiKeysDto)
            - +
            projectId - string - - No -
            createLegacyAccountapiKeysDto - boolean + ApiKeysDto @@ -452,8 +345,8 @@

            - +
            - +
            - +
            - getApiKeysInfo(projectId: string) + getApiKeysInfo(apiKeysDto: ApiKeysDto)
            - +
            projectIdapiKeysDto - string + ApiKeysDto @@ -656,26 +549,26 @@

            - + Async - getProjectIdByPublicKey - + getOwnerIdByPublicKey +
            - getProjectIdByPublicKey(publicKey: any) + getOwnerIdByPublicKey(publicKey: any)
            - +
            - - - - - - - - - - - - - - - - - - - -
            - - - Async - getProjectJwt - - -
            - - getProjectJwt(query: object) -
            - -
            - -
            - Parameters : - - - - - - - - - - - - - - - - - - - -
            NameTypeOptional
            query - object - - No -
            -
            -
            -
            -
            - Returns : unknown - -
            -
            - -
            -
            @@ -809,86 +631,15 @@

            - - - - - - - - - - - - -
            - getPublicKey(projectId: string) -
            - -
            - -
            - Parameters : - - - - - - - - - - - - - - - - - - - -
            NameTypeOptional
            projectId - string - - No -
            -
            -
            -
            -
            - Returns : unknown - -
            -
            - -
            -
            - - - - - - - @@ -909,9 +660,9 @@

            - + @@ -980,9 +731,9 @@

            - + @@ -111,6 +125,10 @@
            Methods
            AsynconModuleInit +
          • + Async + processWebhookQueue +
          • Async start @@ -155,7 +173,7 @@

            Constructor

          • @@ -244,8 +262,8 @@

            @@ -315,8 +333,8 @@

            @@ -396,8 +414,8 @@

            @@ -465,8 +483,8 @@

            @@ -548,8 +566,8 @@

            @@ -565,6 +583,45 @@

            - - - Async - getSandboxKey - - -
            - - getSandboxKey(projectId: string) + getPublicKey(apiKeysDto: ApiKeysDto)
            - +
            projectIdapiKeysDto - string + ApiKeysDto @@ -951,15 +702,15 @@

            - updateSecretKey(projectId: string) + updateSecretKey(apiKeysDto: ApiKeysDto)
            - +
            projectIdapiKeysDto - string + ApiKeysDto @@ -1013,38 +764,40 @@

            -
            import { Inject, Injectable } from '@nestjs/common'
            +        
            import { ApiKey } from '@app/apps-service/api-keys/interfaces/api-keys.interface '
            +import { apiKeyModelString } from '@app/apps-service/api-keys/api-keys.constants'
            +import { Inject, Injectable } from '@nestjs/common'
             import { Model } from 'mongoose'
            -import { ApiKey } from 'apps/charge-api-service/src/api-keys/interfaces/api-keys.interface '
            -import { apiKeyModelString } from 'apps/charge-api-service/src/api-keys/api-keys.constants'
            +import { RpcException } from '@nestjs/microservices'
            +import { isEmpty } from 'lodash'
             import * as bcrypt from 'bcryptjs'
             import * as crypto from 'crypto'
             import base64url from 'base64url'
            -import { RpcException } from '@nestjs/microservices'
            -import { StudioLegacyJwtService } from '@app/api-service/studio-legacy-jwt/studio-legacy-jwt.service'
            -import { isEmpty } from 'lodash'
            +import { ApiKeysDto } from '@app/apps-service/api-keys/dto/api-keys.dto'
             
             @Injectable()
             export class ApiKeysService {
               constructor (
            -    @Inject(apiKeyModelString)
            -    private apiKeyModel: Model<ApiKey>,
            -    private studioLegacyJwtService: StudioLegacyJwtService
            +        @Inject(apiKeyModelString)
            +        private apiKeyModel: Model<ApiKey>
               ) { }
             
            -  async createPublicKey (projectId: string) {
            -    const projectKeys = await this.apiKeyModel.findOne({
            -      projectId
            +  async createPublicKey (apiKeysDto: ApiKeysDto) {
            +    const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName]
            +
            +    const appKeys = await this.apiKeyModel.findOne({
            +      ownerId, appName
                 })
             
            -    if (projectKeys) {
            +    if (appKeys) {
                   throw new RpcException('Public Keys already exist')
                 }
             
                 const publicKey = `pk_${await this.generateRandomToken()}`
             
                 const result = await this.apiKeyModel.create({
            -      projectId,
            +      ownerId,
            +      appName,
                   publicKey
                 })
             
            @@ -1057,8 +810,10 @@ 

            throw new RpcException('Internal Server Error') } - async getPublicKey (projectId: string) { - const apiKeys = await this.findOne({ projectId }) + async getPublicKey (apiKeysDto: ApiKeysDto) { + const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] + + const apiKeys = await this.findOne({ ownerId, appName }) if (apiKeys && apiKeys?.publicKey) { return { publicKey: apiKeys?.publicKey } @@ -1071,8 +826,10 @@

            return this.apiKeyModel.findOne(query) } - async createSecretKey (projectId: string, createLegacyAccount: boolean) { - const apiKeys = await this.apiKeyModel.findOne({ projectId }) + async createSecretKey (apiKeysDto: ApiKeysDto) { + const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] + + const apiKeys = await this.apiKeyModel.findOne({ ownerId, appName }) if (apiKeys && apiKeys?.secretHash) { throw new RpcException('Secret Key already exists') @@ -1082,94 +839,35 @@

            const saltRounds = await bcrypt.genSalt() const secretHash = await bcrypt.hash(secretKey, saltRounds) - if (createLegacyAccount) { - const { encryptedLegacyJwt, legacyBackendAccount } = await this.studioLegacyJwtService.createLegacyJwt(`chargeApp_${projectId}`) - const result = await this.apiKeyModel.findOneAndUpdate( - { projectId }, - { - secretHash, - secretPrefix, - secretLastFourChars, - encryptedLegacyJwt, - legacyBackendAccount - }, - { upsert: true, new: true } - ) - if (result) { - return { - secretKey - } - } - } else { - const result = await this.apiKeyModel.findOneAndUpdate( - { projectId }, - { - secretHash, - secretPrefix, - secretLastFourChars - }, - { upsert: true, new: true } - ) - if (result) { - return { - secretKey - } - } - } - throw new RpcException('Internal Server Error') - } - - async createSandboxKey (projectId: string) { - const projectKeys = await this.apiKeyModel.findOne({ - projectId - }) - - if (projectKeys && projectKeys?.sandboxKey) { - throw new RpcException('sandboxKey Key already exist') - } - const sandboxKey = `pk_test_${await this.generateRandomToken()}` - const result = await this.apiKeyModel.findOneAndUpdate( - { projectId }, + { ownerId, appName }, { - projectId, - sandboxKey - } + secretHash, + secretPrefix, + secretLastFourChars + }, + { upsert: true, new: true } ) if (result) { return { - sandboxKey + secretKey } } throw new RpcException('Internal Server Error') } - async getSandboxKey (projectId: string) { - const apiKeys = await this.findOne({ projectId }) + async updateSecretKey (apiKeysDto: ApiKeysDto) { + const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] - if (apiKeys && apiKeys?.sandboxKey) { - return { sandboxKey: apiKeys?.sandboxKey } - } - - throw new RpcException('Not Found') - } - - async getProjectJwt (query: object) { - const projectApiKeys = await this.apiKeyModel.findOne(query) - const projectEncryptedJwt = projectApiKeys?.encryptedLegacyJwt - return this.studioLegacyJwtService.decryptEncryptedJWT(projectEncryptedJwt) - } - - async updateSecretKey (projectId: string) { const { secretKey, secretPrefix, secretLastFourChars } = await this.generateSecretKey() const saltRounds = await bcrypt.genSalt() const secretHash = await bcrypt.hash(secretKey, saltRounds) const result = await this.apiKeyModel.findOneAndUpdate( { - projectId + ownerId, appName }, { secretHash, @@ -1186,20 +884,22 @@

            } } - async getProjectIdByPublicKey (publicKey: any) { + async getOwnerIdByPublicKey (publicKey: any) { const projectApiKeys: ApiKey | null = await this.apiKeyModel.findOne({ publicKey }) - const projectId: string = projectApiKeys?.projectId?.toString() + const ownerId: string = projectApiKeys?.ownerId?.toString() - if (isEmpty(projectId)) { + if (isEmpty(ownerId)) { return new Error('Project not found') } - return projectId + return ownerId } - async getApiKeysInfo (projectId: string) { + async getApiKeysInfo (apiKeysDto: ApiKeysDto) { + const [ownerId, appName] = [apiKeysDto.ownerId, apiKeysDto.appName] + const projectApiKeys = await this.apiKeyModel.findOne({ - projectId + ownerId, appName }) .select('-secretHash -encryptedLegacyJwt') diff --git a/documentation/injectables/BroadcasterService.html b/documentation/injectables/BroadcasterService.html index 6859ee4b..9e5b0937 100644 --- a/documentation/injectables/BroadcasterService.html +++ b/documentation/injectables/BroadcasterService.html @@ -76,11 +76,25 @@

            Properties

            - +
            - +
            - +
            - +
            - +
            - +
            + + + + + + + + + + + + + + + + + + + +
            + + + Async + processWebhookQueue + + +
            + + processWebhookQueue() +
            + +
            + +
            + Returns : any + +
            +
            @@ -587,8 +644,8 @@

            @@ -610,6 +667,32 @@

            Properties

            +
            - +
            + + + + + + + + + + + + + +
            + + + Private + isProcessing + + +
            + Default value : false +
            + +
            @@ -635,6 +718,72 @@

            + +
            + + + + + + + + + + + + + + + + + +
            + + + Private + Readonly + MAX_RETRY_ATTEMPTS + + +
            + Type : number + +
            + Default value : 6 +
            + +
            + + + + + + + + + + + + + + + +
            + + + Private + Readonly + PROCESSING_INTERVAL_MS + + +
            + Type : number + +
            + Default value : 2000 +
            + +
            @@ -659,7 +808,7 @@

            - +
            - +
            -constructor(dataLayerClient: ClientProxy, httpService: HttpService, configService: ConfigService) +constructor(dataLayerClient: ClientProxy, httpService: HttpService, configService: ConfigService, operatorsService: OperatorsService)
            - +
            operatorsService + OperatorsService + + No +

            @@ -196,6 +212,100 @@

            Constructor

            Methods

            + + + + + + + + + + + + + + + + + + + +
            + + + Private + constructUserOp + + +
            + + constructUserOp(context: ExecutionContext, requestConfig: AxiosRequestConfig, response) +
            + +
            + +
            + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            NameTypeOptional
            context + ExecutionContext + + No +
            requestConfig + AxiosRequestConfig + + No +
            response + + No +
            +
            +
            +
            +
            + Returns : any + +
            +
            + +
            +
            @@ -218,8 +328,8 @@

            @@ -302,8 +412,8 @@

            @@ -366,15 +476,15 @@

            @@ -401,6 +511,15 @@

            + +

            + + + + +
            - +
            - +
            - prepareUrl(environment) + prepareUrl(environment, bundlerProvider)
            - +
            bundlerProvider + No +
            @@ -444,7 +563,7 @@

            - + @@ -477,6 +596,8 @@

            import { ClientProxy } from '@nestjs/microservices' import { callMSFunction } from '@app/common/utils/client-proxy' import { smartWalletsService } from '@app/common/constants/microservices.constants' +import { BundlerProvider } from '@app/api-service/bundler-api/interfaces/bundler.interface' +import { OperatorsService } from '@app/accounts-service/operators/operators.service' @Injectable() export class BundlerApiInterceptor implements NestInterceptor { @@ -484,7 +605,8 @@

            constructor ( @Inject(smartWalletsService) private readonly dataLayerClient: ClientProxy, private httpService: HttpService, - private configService: ConfigService + private configService: ConfigService, + private operatorsService: OperatorsService ) { } async intercept (context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { @@ -492,32 +614,56 @@

            context ) + const isSponsoredQuotaExceeded = await this.operatorsService.isOperatorSponsoredQuotaExceeded(context, requestConfig) + if (isSponsoredQuotaExceeded) { + throw new HttpException('Operator sponsored transaction quota exceeded', HttpStatus.BAD_REQUEST) + } + const response = await lastValueFrom( this.httpService .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) }) ) ) if (requestConfig.data?.method === 'eth_sendUserOperation') { - const userOp = { ...requestConfig.data.params[0], userOpHash: response?.result, apiKey: context.switchToHttp().getRequest().query.apiKey } + const userOp = this.constructUserOp(context, requestConfig, response) this.logger.log(`eth_sendUserOperation: ${JSON.stringify(userOp)}`) try { if (isNil(userOp.userOpHash)) { @@ -539,10 +685,11 @@

            private async prepareRequestConfig (context: ExecutionContext) { const request = context.switchToHttp().getRequest() const requestEnvironment = request.environment + const bundlerProvider = request.query?.provider ?? BundlerProvider.ETHERSPOT const ctxHandlerName = context.getHandler().name const body = request.body const requestConfig: AxiosRequestConfig = { - url: this.prepareUrl(requestEnvironment), + url: this.prepareUrl(requestEnvironment, bundlerProvider), method: ctxHandlerName } @@ -553,9 +700,9 @@

            return requestConfig } - private prepareUrl (environment) { + private prepareUrl (environment, bundlerProvider) { if (isEmpty(environment)) throw new InternalServerErrorException('Bundler environment is missing') - const config = this.configService.get(`bundler.${environment}`) + const config = this.configService.get(`bundler.${bundlerProvider}.${environment}`) if (config.url) { return config.url @@ -563,6 +710,29 @@

            throw new InternalServerErrorException(`${capitalize(environment)} bundler environment is missing`) } } + + private constructUserOp (context: ExecutionContext, requestConfig: AxiosRequestConfig, response) { + const request = context.switchToHttp().getRequest() + const bundlerProvider = request.query?.provider ?? BundlerProvider.ETHERSPOT + const param = requestConfig?.data?.params?.[0] + const base = { userOpHash: response?.result, apiKey: request.query.apiKey } + + if (!param) { + throw new InternalServerErrorException('UserOp param is missing') + } + + if (bundlerProvider === BundlerProvider.PIMLICO) { + return { + ...param, + ...base, + initCode: param.initCode ?? '0x', + sponsorId: param.paymaster ? BundlerProvider.PIMLICO : undefined, + paymasterAndData: param.paymasterData, + paymasterData: undefined + } + } + return { ...param, ...base } + } }

            diff --git a/documentation/injectables/ConsensusService.html b/documentation/injectables/ConsensusService.html index aebd247b..c9929551 100644 --- a/documentation/injectables/ConsensusService.html +++ b/documentation/injectables/ConsensusService.html @@ -146,10 +146,6 @@

            Methods
            Async fetchValidatorsMap -
          • - Async - fetchValidatorsTimeout -
          • Private Async @@ -410,8 +406,8 @@

            - + @@ -482,8 +478,8 @@

            - + @@ -613,8 +609,8 @@

            - + @@ -692,8 +688,8 @@

            - + @@ -787,8 +783,8 @@

            - + @@ -882,8 +878,8 @@

            - + @@ -989,8 +985,8 @@

            - + @@ -1072,8 +1068,8 @@

            - + @@ -1143,8 +1139,8 @@

            - + @@ -1226,8 +1222,8 @@

            - + @@ -1265,7 +1261,7 @@

            - Returns : { firstSeen: any; forDelegation: any; totalValidated: any; uptime: any; } + Returns : { firstSeen: any; forDelegation: boolean; totalValidated: any; uptime: any; }
            @@ -1298,8 +1294,8 @@

            - + @@ -1377,8 +1373,8 @@

            - + @@ -1394,52 +1390,6 @@

            - - - - - - - - - - - - - - - - - - - - - - -
            - - - Async - fetchValidatorsTimeout - - -
            - - fetchValidatorsTimeout() -
            - Decorators : -
            - @Timeout(5000)
            -
            - -
            - -
            - Returns : any - -
            -
            @@ -1463,8 +1413,8 @@

            @@ -1534,8 +1484,8 @@

            @@ -1574,8 +1524,8 @@

            @@ -1652,8 +1602,8 @@

            @@ -1724,8 +1674,8 @@

            @@ -1802,8 +1752,8 @@

            @@ -1849,8 +1799,8 @@

            @@ -1928,8 +1878,8 @@

            @@ -2007,8 +1957,8 @@

            @@ -2054,8 +2004,8 @@

            @@ -2208,8 +2158,8 @@

            @@ -2280,8 +2230,8 @@

            @@ -2351,8 +2301,8 @@

            @@ -2423,8 +2373,8 @@

            @@ -2502,8 +2452,8 @@

            @@ -2549,8 +2499,8 @@

            @@ -2596,8 +2546,8 @@

            @@ -2643,8 +2593,8 @@

            @@ -2683,8 +2633,8 @@

            @@ -2762,8 +2712,8 @@

            @@ -2809,8 +2759,8 @@

            @@ -2848,8 +2798,8 @@

            @@ -2964,7 +2914,7 @@

            @@ -2986,7 +2936,7 @@

            @@ -3008,7 +2958,7 @@

            @@ -3030,7 +2980,7 @@

            @@ -3052,7 +3002,7 @@

            @@ -3078,7 +3028,7 @@

            formatEther, formatUnits } from 'nestjs-ethers' -import { Cron, CronExpression, Timeout } from '@nestjs/schedule' +import { Cron, CronExpression } from '@nestjs/schedule' import { CACHE_MANAGER } from '@nestjs/cache-manager' import { Cache } from 'cache-manager' import { isEmpty, isUndefined } from 'lodash' @@ -3118,12 +3068,6 @@

            return validatorsInfo } - @Timeout(5000) - async fetchValidatorsTimeout () { - // Called once after 5 seconds - await this.handleValidatorsUpdate() - } - private get multiCallAddress (): string { return this.configService.getOrThrow('multiCallAddress') } @@ -3325,7 +3269,7 @@

            private extendActiveMetadata (nodeMetadata: any) { return { firstSeen: nodeMetadata?.firstSeen, - forDelegation: nodeMetadata?.forDelegation, + forDelegation: true, totalValidated: nodeMetadata?.totalValidated, uptime: nodeMetadata?.upTime } diff --git a/documentation/injectables/DataLayerService.html b/documentation/injectables/DataLayerService.html index 0d2e64f9..63e3197a 100644 --- a/documentation/injectables/DataLayerService.html +++ b/documentation/injectables/DataLayerService.html @@ -377,7 +377,7 @@

            @@ -406,7 +406,7 @@

            - + @@ -416,6 +416,18 @@

            +

            + + + + + + +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - findSponsoredTransactionsCount(sponsorId: string) + findSponsoredTransactionsCount(apiKey: string, startDate?: string)
            sponsorIdapiKey string
            startDate + string + + Yes +
            @@ -455,8 +467,8 @@

            - + @@ -697,8 +709,8 @@

            - + @@ -1038,7 +1050,7 @@

            async recordUserOp (baseUserOp: BaseUserOp) { try { - if (baseUserOp.paymasterAndData !== '0x') { + if (baseUserOp.paymasterAndData && baseUserOp.paymasterAndData !== '0x' && !baseUserOp.sponsorId) { const paymasterAddressAndSponsorId = decodePaymasterAndData(baseUserOp.paymasterAndData) baseUserOp.paymaster = paymasterAddressAndSponsorId.paymasterAddress baseUserOp.sponsorId = paymasterAddressAndSponsorId.sponsorId @@ -1225,13 +1237,21 @@

            } } - async findSponsoredTransactionsCount (sponsorId: string): Promise<number> { - return this.userOpModel.countDocuments({ sponsorId: { $eq: sponsorId } }) + async findSponsoredTransactionsCount (apiKey: string, startDate?: string): Promise<number> { + return this.userOpModel.countDocuments({ + apiKey: { $eq: apiKey }, + sponsorId: { $ne: '0' }, + createdAt: { $gte: startDate ? new Date(startDate) : new Date(0) } + }) } async handleUserOpAndWalletActionOfOperatorToSendAnalyticsEvent (body) { try { - const user = await this.getOperatorByApiKey(body.userOp.apiKey) + const operatorUser = await this.getOperatorByApiKey(body.userOp.apiKey) + if (!operatorUser || !operatorUser?.user) { + return + } + const { user } = operatorUser if (!get(user, 'auth0Id')) { return } @@ -1271,7 +1291,10 @@

            this.logger.log('Operator didnt exists') return false } - return user + return { + operator, + user + } } catch (error) { this.logger.error(error) } diff --git a/documentation/injectables/ERC20EventsScannerService.html b/documentation/injectables/ERC20EventsScannerService.html index f698aae2..b21b4a6d 100644 --- a/documentation/injectables/ERC20EventsScannerService.html +++ b/documentation/injectables/ERC20EventsScannerService.html @@ -96,10 +96,6 @@

            Methods
            Async processEvent

          • -
          • - Async - processEventWithRetry -
          • Async fetchLogs @@ -135,7 +131,7 @@

            Constructor

            - + @@ -292,8 +288,8 @@

            - + @@ -400,7 +396,7 @@

            -
            Defined in ScannerService:39 +
            Defined in ScannerService:38
            @@ -496,7 +492,7 @@

            -
            Defined in EventsScannerService:65 + @@ -545,95 +541,6 @@

            - - - - - - - - - - - - - - - - - - - -
            - - - Async - processEventWithRetry - - -
            - - processEventWithRetry(log: Log, retries: number) -
            - -
            - -
            - Parameters : - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            NameTypeOptionalDefault value
            log - Log - - No - -
            retries - number - - No - - 3 -
            -
            -
            -
            -
            - Returns : any - -
            -
            - -
            -
            @@ -835,7 +742,6 @@

            import { GasService } from '@app/common/services/gas.service' import { CACHE_MANAGER } from '@nestjs/cache-manager' import { Cache } from 'cache-manager' -import { chunk } from 'lodash' @Injectable() export class ERC20EventsScannerService extends EventsScannerService { @@ -861,23 +767,14 @@

            if (fromBlock > toBlock) return const logs = await this.fetchLogs(fromBlock, toBlock) - const batches = chunk(logs, 10) // Process in batches of 10 - - for (const batch of batches) { - await Promise.all(batch.map(log => this.processEventWithRetry(log))) - } - } - async processEventWithRetry (log: Log, retries = 3) { - for (let i = 0; i < retries; i++) { + for (const log of logs) { try { await this.processEvent(log) - return } catch (error) { - if (i === retries - 1) { - this.logger.error(`Failed to process event after ${retries} attempts:`, error) - this.logger.error({ log }) - } + this.logger.error('Failed to process log:') + this.logger.error({ log }) + this.logger.error(error) } } } diff --git a/documentation/injectables/EventsScannerService.html b/documentation/injectables/EventsScannerService.html index 58b38444..bacf45dc 100644 --- a/documentation/injectables/EventsScannerService.html +++ b/documentation/injectables/EventsScannerService.html @@ -594,9 +594,9 @@

            try { await this.processEvent(log) } catch (error) { - // this.logger.error('Failed to process log:') - // this.logger.error({ log }) - // this.logger.error(error) + this.logger.error('Failed to process log:') + this.logger.error({ log }) + this.logger.error(error) } } } diff --git a/documentation/injectables/ExplorerService.html b/documentation/injectables/ExplorerService.html index 1493c638..8fcaa3a2 100644 --- a/documentation/injectables/ExplorerService.html +++ b/documentation/injectables/ExplorerService.html @@ -152,7 +152,7 @@

            Constructor

            @@ -241,8 +241,8 @@

            @@ -312,8 +312,8 @@

            @@ -408,8 +408,8 @@

            @@ -483,7 +483,7 @@

            @@ -512,7 +512,7 @@

            @@ -534,7 +534,7 @@

            @@ -556,7 +556,7 @@

            @@ -578,7 +578,7 @@

            @@ -598,8 +598,9 @@

            import { HttpService } from '@nestjs/axios' import { NATIVE_FUSE_TOKEN } from '@app/smart-wallets-service/common/constants/fuseTokenInfo' import { ethers } from 'ethers' -import { getCollectiblesByOwner } from '@app/network-service/common/constants/graph-queries/nfts' +import { getCollectiblesByOwner } from '@app/network-service/common/constants/graph-queries/nfts-v3' import { isEmpty } from 'lodash' +import { ExplorerServiceCollectibleResponse, ExplorerServiceGraphQLVariables, ExplorerServiceTransformedCollectible } from '../interfaces/balances.interface' @Injectable() export class ExplorerService implements BalanceService { @@ -608,7 +609,7 @@

            private readonly httpService: HttpService, private readonly configService: ConfigService, private readonly graphQLService: GraphQLService - ) {} + ) { } get explorerBaseUrl () { return this.configService.get('explorer.baseUrl') @@ -664,7 +665,7 @@

            async getERC721TokenBalances (address: string, limit?: number, cursor?: string) { const query = getCollectiblesByOwner - const variables: any = { + const variables: ExplorerServiceGraphQLVariables = { address: address.toLowerCase(), orderBy: 'created', orderDirection: 'desc', @@ -676,16 +677,48 @@

            } const data = await this.graphQLService.fetchFromGraphQL(this.nftGraphUrl, query, variables) + + if (!data?.data?.account) { + return { + nextCursor: null, + data: { + account: { address, id: address, collectibles: [] } + } + } + } + const collectibles = data?.data?.account?.collectibles || [] + + const transformedCollectibles = collectibles.map((collectible: ExplorerServiceCollectibleResponse): ExplorerServiceTransformedCollectible => ({ + collection: collectible?.collection, + created: collectible?.created, + creator: collectible?.creator, + owner: collectible?.owner, + tokenId: collectible?.tokenId, + description: collectible?.metadata?.description ?? null, + descriptorUri: collectible?.contentURI ?? null, + imageURL: collectible?.metadata?.imageURL ?? null, + name: collectible?.metadata?.name ?? null, + id: collectible?.tokenId && collectible?.collection?.collectionAddress + ? `${collectible.collection.collectionAddress}-0x${BigInt(collectible.tokenId).toString(16)}` + : null + })) + const nextCursor = isEmpty(collectibles) ? null : collectibles.length === variables.first - ? Buffer.from((variables.skip || 0) + collectibles.length + '').toString('base64') + ? Buffer.from(`${(variables.skip || 0) + collectibles.length}`).toString('base64') : null return { nextCursor, - ...data + data: { + account: { + address, + id: address, + collectibles: transformedCollectibles + } + } } } } diff --git a/documentation/injectables/GraphService.html b/documentation/injectables/GraphService.html index 10deb2f2..5ea1c5bd 100644 --- a/documentation/injectables/GraphService.html +++ b/documentation/injectables/GraphService.html @@ -81,6 +81,11 @@

            Properties
            ReadonlyblockClient +
          • + Private + Readonly + masterChefV3Client +
          • Private Readonly @@ -106,6 +111,9 @@
            Methods
          • getBlockClient
          • +
          • + getMasterChefV3Client +
          • getVoltageClient
          • @@ -135,7 +143,7 @@

            Constructor

            @@ -198,8 +206,45 @@

            + + + + + + + +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - +
            - + +
            + +
            + Returns : GraphQLClient + +
            +
            + + + + + + + + + + + + @@ -235,8 +280,8 @@

            @@ -272,8 +317,8 @@

            @@ -321,6 +366,34 @@

            +

            +
            + + + getMasterChefV3Client + + +
            +getMasterChefV3Client() +
            +
            - +
            - +
            + + + + + + + + + + + + +
            + + + Private + Readonly + masterChefV3Client + + +
            + Type : GraphQLClient + +
            + +
            @@ -394,13 +467,21 @@

            private readonly voltBarClient: GraphQLClient private readonly blockClient: GraphQLClient private readonly voltageClient: GraphQLClient - - constructor ( - private configService: ConfigService - ) { - this.voltBarClient = new GraphQLClient(this.configService.get('voltBarGraphUrl')) - this.blockClient = new GraphQLClient(this.configService.get('blockGraphUrl')) - this.voltageClient = new GraphQLClient(this.configService.get('voltageGraphUrl')) + private readonly masterChefV3Client: GraphQLClient + + constructor (private configService: ConfigService) { + this.voltBarClient = new GraphQLClient( + this.configService.get('voltBarGraphUrl') + ) + this.blockClient = new GraphQLClient( + this.configService.get('blockGraphUrl') + ) + this.voltageClient = new GraphQLClient( + this.configService.get('voltageGraphUrl') + ) + this.masterChefV3Client = new GraphQLClient( + this.configService.get('masterChefV3GraphUrl') + ) } getVoltBarClient () { @@ -414,6 +495,10 @@

            getVoltageClient () { return this.voltageClient } + + getMasterChefV3Client () { + return this.masterChefV3Client + } } diff --git a/documentation/injectables/LiquidStakingFuseClient.html b/documentation/injectables/LiquidStakingFuseClient.html new file mode 100644 index 00000000..ae0ec33b --- /dev/null +++ b/documentation/injectables/LiquidStakingFuseClient.html @@ -0,0 +1,290 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
            +
            + + +
            +
            + + + + + + + + + + + +
            +
            +

            +

            File

            +

            +

            + apps/charge-network-service/src/voltage-dex/services/liquid-staking-fuse-client.service.ts +

            + + + + + +
            +

            Index

            +

            + + + + + + + + + + + + + + +
            +
            Methods
            +
            + +
            + + +
            +

            Constructor

            + + + + + + + + + + + + + +
            +constructor(graphClient: GraphQLClient) +
            + +
            +
            + Parameters : + + + + + + + + + + + + + + + + + + +
            NameTypeOptional
            graphClient + GraphQLClient + + No +
            +
            +
            +
            + +
            + +

            + Methods +

            + + + + + + + + + + + + + + + + + + + +
            + + + Async + getRatio + + +
            + + getRatio() +
            + +
            + +
            + Returns : unknown + +
            +
            +
            + +

            + + +
            +
            import { gql, GraphQLClient } from 'graphql-request'
            +import { Injectable } from '@nestjs/common'
            +import { formatEther } from 'nestjs-ethers'
            +
            +@Injectable()
            +export class LiquidStakingFuseClient {
            +  constructor (private graphClient: GraphQLClient) {}
            +
            +  async getRatio () {
            +    const query = gql`
            +      query ratio {
            +        liquidStakings {
            +          ratio
            +        }
            +      }
            +    `
            +
            +    const data = await this.graphClient.request<{ liquidStakings: { ratio: string }[] }>(query)
            +    return formatEther(data?.liquidStakings?.[0]?.ratio ?? '0')
            +  }
            +}
            +
            +
            + +

            + + + + + + + + + + + + +

          • +
            +

            results matching ""

            +
              +
              +
              +

              No results matching ""

              +
              +
              +

              + +

              +

              + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/injectables/OperatorJwtStrategy.html b/documentation/injectables/OperatorJwtStrategy.html index 2f0ea8cb..52039b41 100644 --- a/documentation/injectables/OperatorJwtStrategy.html +++ b/documentation/injectables/OperatorJwtStrategy.html @@ -77,6 +77,11 @@
              Methods
                +
              • + Private + Static + extractJwtFromCookies +
              • validate
              • @@ -103,7 +108,7 @@

                Constructor

                - + @@ -146,6 +151,78 @@

                Constructor

                Methods

                + + + + + + + + + + + + + + + + + + + +
                + + + Private + Static + extractJwtFromCookies + + +
                + + extractJwtFromCookies(request: Request) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                request + Request + + No +
                +
                +
                +
                +
                + Returns : string | undefined + +
                +
                + +
                +
                @@ -166,8 +243,8 @@

                @@ -215,6 +292,7 @@

                import { ExtractJwt, Strategy } from 'passport-jwt' import { operatorJwtString } from '@app/accounts-service/operators/operators.constants' import { ConfigService } from '@nestjs/config' +import { Request } from 'express' @Injectable() export class OperatorJwtStrategy extends PassportStrategy(Strategy, operatorJwtString) { @@ -222,11 +300,17 @@

                private readonly configService: ConfigService ) { super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + jwtFromRequest: ExtractJwt.fromExtractors([ + OperatorJwtStrategy.extractJwtFromCookies + ]), secretOrKey: configService.get('SMART_WALLETS_JWT_SECRET') }) } + private static extractJwtFromCookies (request: Request): string | undefined { + return request.cookies?.operator_access_token + } + validate (payload: unknown): unknown { return payload } diff --git a/documentation/injectables/OperatorsService.html b/documentation/injectables/OperatorsService.html index b5f919ec..031fcea4 100644 --- a/documentation/injectables/OperatorsService.html +++ b/documentation/injectables/OperatorsService.html @@ -105,20 +105,39 @@

                Methods
                AsynccheckOperatorExistenceByEoaAddress +
              • + Async + checkout +
              • Async checkWalletActivationStatus
              • +
              • + Async + compareRefreshToken +
              • Private constructUserProjectResponse
              • +
              • + Async + createChargeBridge +
              • +
              • + Async + createInvoice +
              • +
              • + Async + createOperatorJwtTokens +
              • Async createOperatorUserAndProjectAndWallet
              • - Private Async createOperatorWallet
              • @@ -137,6 +156,14 @@
                Methods
                AsynccreateProjectSecret +
              • + Async + createRefreshToken +
              • +
              • + Async + createSubscription +
              • Private Async @@ -150,10 +177,34 @@
                Methods
                Private errorHandler
              • +
              • + Async + findAllOperatorWallets +
              • +
              • + Async + findCheckout +
              • +
              • + Async + findFirstDayOfMonthInvoice +
              • +
              • + Async + findInvoices +
              • +
              • + Async + findLastPaidCheckout +
              • Async findOperatorBySmartWallet
              • +
              • + Async + findRefreshToken +
              • Async findWalletOwner @@ -166,6 +217,10 @@
                Methods
                Async getBalance
              • +
              • + Async + getCheckoutSessions +
              • Async getOperatorUserAndProject @@ -174,33 +229,98 @@
                Methods
                Async getSponsoredTransactionsCount
              • +
              • + Async + getSubscriptions +
              • Async googleFormSubmit
              • +
              • + Async + handleCheckoutWebhook +
              • Async handleWebhookReceiveAndFundPaymasterAndDeleteWalletAddressFromOperatorsWebhook
              • +
              • + Async + hashRefreshToken +
              • +
              • + Async + invalidateRefreshTokens +
              • +
              • + Async + isOperatorSponsoredQuotaExceeded +
              • +
              • + Async + markRefreshTokenAsUsed +
              • +
              • + Async + migrateOperatorWallet +
              • Async operatorAccountActivationEvent
              • +
              • + Async + operatorSubscriptionPaidEvent +
              • Async predictWallet
              • +
              • + Async + processMonthlyBilling +
              • +
              • + Async + processMonthlySubscriptions +
              • +
              • + Private + Async + retryGetTokenPrice +
              • +
              • + subscriptionInfo +
              • +
              • + Async + subscriptionWeb3 +
              • +
              • + Async + updateCheckout +
              • Async updateIsActivated
              • + Async + updateIsActivatedByOwnerId +
              • +
              • + Async validate
              • Private validateInput
              • +
              • + Async + validateRefreshToken +
              • @@ -219,12 +339,12 @@

                Constructor

                @@ -350,10 +470,58 @@

                Constructor

                - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -468,8 +636,8 @@

                @@ -539,8 +707,8 @@

                @@ -592,26 +760,26 @@

                @@ -626,12 +794,28 @@

                + + + + + + + + + + @@ -693,64 +877,12 @@

                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -818,9 +950,9 @@

                - + - + @@ -861,27 +993,26 @@

                @@ -904,7 +1035,7 @@

                - + + + + + + + + + + + + + + + + +
                - +
                -constructor(jwtService: JwtService, usersService: UsersService, configService: ConfigService, operatorWalletModel: Model<OperatorWallet>, paymasterService: PaymasterService, projectsService: ProjectsService, dataLayerClient: ClientProxy, notificationsClient: ClientProxy, analyticsService: AnalyticsService, httpService: HttpService) +constructor(jwtService: JwtService, usersService: UsersService, configService: ConfigService, operatorWalletModel: Model<OperatorWallet>, paymasterService: PaymasterService, projectsService: ProjectsService, dataLayerClient: ClientProxy, notificationsClient: ClientProxy, analyticsService: AnalyticsService, operatorRefreshTokenModel: Model, operatorInvoiceModel: Model<OperatorInvoice>, operatorCheckoutModel: Model<OperatorCheckout>, chargeBridgeModel: Model<ChargeBridge>, tradeService: TradeService)
                - +
                httpServiceoperatorRefreshTokenModel + Model<OperatorRefreshToken> + + No +
                operatorInvoiceModel + Model<OperatorInvoice> + + No +
                operatorCheckoutModel + Model<OperatorCheckout> + + No +
                chargeBridgeModel + Model<ChargeBridge> + + No +
                tradeService - HttpService + TradeService @@ -397,8 +565,8 @@

                - +
                - +
                - +
                - + Async - checkWalletActivationStatus - + checkout +
                - checkWalletActivationStatus(auth0Id) + checkout(auth0Id: string, createOperatorCheckoutDto: CreateOperatorCheckoutDto)
                - +
                NameType Optional
                auth0Id + string + + No +
                createOperatorCheckoutDto + CreateOperatorCheckoutDto + No @@ -659,26 +843,26 @@

                - + - Private - constructUserProjectResponse - + Async + checkWalletActivationStatus +
                - constructUserProjectResponse(user: any, projectObject: any, publicKey: string, secretKey: string, sponsorId: string) + checkWalletActivationStatus(auth0Id)
                - +
                NameType Optional
                user - any - - No -
                projectObject - any - - No -
                publicKey - string - - No -
                secretKey - string - - No -
                sponsorId - string - auth0Id No @@ -764,7 +896,7 @@

                - Returns : { user: { id: any; name: any; email: any; auth0Id: any; }; project: { id: any; ownerId: any; name: any; description: any; publicKey: string; secretKey: string; sponsorId: string; }; } + Returns : unknown
                @@ -778,26 +910,26 @@

                - + Async - createOperatorUserAndProjectAndWallet - + compareRefreshToken +
                - createOperatorUserAndProjectAndWallet(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) + compareRefreshToken(plainToken: string, hashedToken: string)
                - +
                createOperatorUserDtoplainToken - CreateOperatorUserDto + string @@ -830,7 +962,7 @@

                auth0IdhashedToken string
                - + Private - Async - createOperatorWallet - + constructUserProjectResponse +
                - createOperatorWallet(user: any, predictedWallet: string) + constructUserProjectResponse(user: User, project: OperatorProject, wallet?: OperatorWallet, secretKey?: string)
                - +
                user - any + User @@ -914,9 +1045,9 @@

                predictedWalletproject - string + OperatorProject @@ -924,6 +1055,30 @@

                wallet + OperatorWallet + + Yes +
                secretKey + string + + Yes +
                @@ -931,7 +1086,7 @@

                - Returns : any + Returns : OperatorUserProjectResponse
                @@ -945,27 +1100,26 @@

                - + - Private Async - createPaymasters - + createChargeBridge + - createPaymasters(projectObject: any) + createChargeBridge(auth0Id: string, createChargeBridgeDto: CreateChargeBridgeDto) - + @@ -986,9 +1140,21 @@

                - projectObject + auth0Id - any + string + + + + No + + + + + + createChargeBridgeDto + + CreateChargeBridgeDto @@ -1017,27 +1183,26 @@

                - + - Private Async - createProject - + createInvoice + - createProject(user: any, createOperatorUserDto: CreateOperatorUserDto) + createInvoice(ownerId: string, amount: number, currency: string, txHash: string, amountUsd?: number) - + @@ -1058,9 +1223,9 @@

                - user + ownerId - any + string @@ -1070,9 +1235,33 @@

                - createOperatorUserDto + amount + + number + + + + No + + + + + + currency + + string + + + + No + + + + + + txHash - CreateOperatorUserDto + string @@ -1080,6 +1269,18 @@

                + + + amountUsd + + number + + + + Yes + + + @@ -1087,7 +1288,7 @@

                - Returns : unknown + Returns : Promise<OperatorInvoice>
                @@ -1101,27 +1302,26 @@

                - + - Private Async - createProjectSecret - + createOperatorJwtTokens + - createProjectSecret(projectObject: any) + createOperatorJwtTokens(auth0Id: string, response: Response) - + @@ -1142,9 +1342,21 @@

                - projectObject + auth0Id - any + string + + + + No + + + + + + response + + Response @@ -1173,27 +1385,26 @@

                - + - Private Async - createUser - + createOperatorUserAndProjectAndWallet + - createUser(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) + createOperatorUserAndProjectAndWallet(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) - + @@ -1216,7 +1427,7 @@

                createOperatorUserDto - CreateOperatorUserDto + CreateOperatorUserDto @@ -1257,26 +1468,26 @@

                - + Async - deleteAddressFromOperatorsWebhook - + createOperatorWallet + - deleteAddressFromOperatorsWebhook(walletAddress: string) + createOperatorWallet(createOperatorWalletDto: CreateOperatorWalletDto, auth0Id: string) - + @@ -1297,7 +1508,19 @@

                - walletAddress + createOperatorWalletDto + + CreateOperatorWalletDto + + + + No + + + + + + auth0Id string @@ -1328,26 +1551,27 @@

                - + Private - errorHandler - + Async + createPaymasters + - errorHandler(error: any) + createPaymasters(projectObject: any) - + @@ -1368,7 +1592,7 @@

                - error + projectObject any @@ -1385,7 +1609,7 @@

                - Returns : void + Returns : unknown
                @@ -1399,26 +1623,27 @@

                - + + Private Async - findOperatorBySmartWallet - + createProject + - findOperatorBySmartWallet(value: string) + createProject(user: any, createOperatorUserDto: CreateOperatorUserDto) - + @@ -1439,9 +1664,21 @@

                - value + user - string + any + + + + No + + + + + + createOperatorUserDto + + CreateOperatorUserDto @@ -1456,7 +1693,7 @@

                - Returns : Promise<OperatorWallet> + Returns : unknown
                @@ -1470,26 +1707,27 @@

                - + + Private Async - findWalletOwner - + createProjectSecret + - findWalletOwner(value: string) + createProjectSecret(projectObject: any) - + @@ -1510,9 +1748,9 @@

                - value + projectObject - string + any @@ -1527,7 +1765,7 @@

                - Returns : Promise<OperatorWallet> + Returns : unknown
                @@ -1541,26 +1779,26 @@

                - + Async - fundPaymaster - + createRefreshToken + - fundPaymaster(sponsorId: string, amount: string, ver: string, environment: string) + createRefreshToken(auth0Id: string, refreshToken: string) - + @@ -1581,31 +1819,7 @@

                - sponsorId - - string - - - - No - - - - - - amount - - string - - - - No - - - - - - ver + auth0Id string @@ -1617,7 +1831,7 @@

                - environment + refreshToken string @@ -1634,7 +1848,7 @@

                - Returns : Promise<any> + Returns : Promise<OperatorRefreshToken>
                @@ -1648,26 +1862,26 @@

                - + Async - getBalance - + createSubscription + - getBalance(address: string, ver: string, environment: string) + createSubscription(auth0Id: string) - + @@ -1688,31 +1902,7 @@

                - address - - string - - - - No - - - - - - ver - - string - - - - No - - - - - - environment + auth0Id string @@ -1729,7 +1919,7 @@

                - Returns : Promise<string> + Returns : Promise<OperatorInvoice>
                @@ -1743,26 +1933,27 @@

                - + + Private Async - getOperatorUserAndProject - + createUser + - getOperatorUserAndProject(auth0Id: string) + createUser(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) - + @@ -1782,6 +1973,18 @@

                + + createOperatorUserDto + + CreateOperatorUserDto + + + + No + + + + auth0Id @@ -1814,26 +2017,26 @@

                - + Async - getSponsoredTransactionsCount - + deleteAddressFromOperatorsWebhook + - getSponsoredTransactionsCount(auth0Id: string) + deleteAddressFromOperatorsWebhook(walletAddress: string) - + @@ -1854,7 +2057,7 @@

                - auth0Id + walletAddress string @@ -1885,26 +2088,1119 @@

                - + - Async - googleFormSubmit - + Private + errorHandler + - googleFormSubmit(createOperatorUserDto: CreateOperatorUserDto) + errorHandler(error: any) - + + + + + + + + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                error + any + + No +
                +
                +
                +
                +
                + Returns : void + +
                +
                + +
                + + + + + + + + + + + + + + + + + + + + + + + +
                + + + Async + findAllOperatorWallets + + +
                + + findAllOperatorWallets() +
                + +
                + +
                + Returns : Promise<OperatorWallet[]> + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findCheckout + + +
                + + findCheckout(sessionId: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                sessionId + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findFirstDayOfMonthInvoice + + +
                + + findFirstDayOfMonthInvoice(ownerId: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                ownerId + string + + No +
                +
                +
                +
                +
                + Returns : Promise<OperatorInvoice> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findInvoices + + +
                + + findInvoices(ownerId: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                ownerId + string + + No +
                +
                +
                +
                +
                + Returns : Promise<OperatorInvoice[]> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findLastPaidCheckout + + +
                + + findLastPaidCheckout(ownerId: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                ownerId + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findOperatorBySmartWallet + + +
                + + findOperatorBySmartWallet(value: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                value + string + + No +
                +
                +
                +
                +
                + Returns : Promise<OperatorWallet> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findRefreshToken + + +
                + + findRefreshToken(auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : Promise<OperatorRefreshToken> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + findWalletOwner + + +
                + + findWalletOwner(value: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                value + string + + No +
                +
                +
                +
                +
                + Returns : Promise<OperatorWallet> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + fundPaymaster + + +
                + + fundPaymaster(sponsorId: string, amount: string, ver: string, environment: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                sponsorId + string + + No +
                amount + string + + No +
                ver + string + + No +
                environment + string + + No +
                +
                +
                +
                +
                + Returns : Promise<any> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getBalance + + +
                + + getBalance(address: string, ver: string, environment: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                address + string + + No +
                ver + string + + No +
                environment + string + + No +
                +
                +
                +
                +
                + Returns : Promise<string> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getCheckoutSessions + + +
                + + getCheckoutSessions(auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getOperatorUserAndProject + + +
                + + getOperatorUserAndProject(auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getSponsoredTransactionsCount + + +
                + + getSponsoredTransactionsCount(auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getSubscriptions + + +
                + + getSubscriptions(auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : Promise<OperatorInvoice[]> + +
                +
                + +
                +
                + + + + + + + + + + + + @@ -1927,13 +3223,963 @@

                + + + + + + +
                + + + Async + googleFormSubmit + + +
                + + googleFormSubmit(createOperatorUserDto: CreateOperatorUserDto) +
                +
                createOperatorUserDto - CreateOperatorUserDto + CreateOperatorUserDto + + No +
                +

                +
                +
                +
                + Returns : any + +
                +
                + +
                + + + + + + + + + + + + + + + + + + + + + + + +
                + + + Async + handleCheckoutWebhook + + +
                + + handleCheckoutWebhook(webhookEvent: ChargeCheckoutWebhookEvent) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                webhookEvent + ChargeCheckoutWebhookEvent + + No +
                +
                +
                +
                +
                + Returns : any + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + handleWebhookReceiveAndFundPaymasterAndDeleteWalletAddressFromOperatorsWebhook + + +
                + + handleWebhookReceiveAndFundPaymasterAndDeleteWalletAddressFromOperatorsWebhook(webhookEvent: WebhookEvent) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                webhookEvent + WebhookEvent + + No +
                +
                +
                +
                +
                + Returns : Promise<void> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + hashRefreshToken + + +
                + + hashRefreshToken(token: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                token + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + invalidateRefreshTokens + + +
                + + invalidateRefreshTokens(auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + isOperatorSponsoredQuotaExceeded + + +
                + + isOperatorSponsoredQuotaExceeded(context: ExecutionContext, requestConfig: AxiosRequestConfig) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                context + ExecutionContext + + No +
                requestConfig + AxiosRequestConfig + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + markRefreshTokenAsUsed + + +
                + + markRefreshTokenAsUsed(refreshToken: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                refreshToken + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + migrateOperatorWallet + + +
                + + migrateOperatorWallet(migrateOperatorWalletDto: CreateOperatorWalletDto, auth0Id: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                migrateOperatorWalletDto + CreateOperatorWalletDto + + No +
                auth0Id + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + operatorAccountActivationEvent + + +
                + + operatorAccountActivationEvent(undefined) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + +
                NameOptional
                + No +
                +
                +
                +
                +
                + Returns : any + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + operatorSubscriptionPaidEvent + + +
                + + operatorSubscriptionPaidEvent(undefined) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + +
                NameOptional
                + No +
                +
                +
                +
                +
                + Returns : any + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + predictWallet + + +
                + + predictWallet(owner: string, index: number, ver: string, environment: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                owner + string + + No +
                index + number + + No +
                ver + string + + No +
                environment + string + + No +
                +
                +
                +
                +
                + Returns : Promise<string> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + processMonthlyBilling + + +
                + + processMonthlyBilling() +
                + +
                + +
                + Returns : any + +
                +
                + + + + + + + + + + + + + + + + + + + + + + +
                + + + Async + processMonthlySubscriptions + + +
                + + processMonthlySubscriptions() +
                + Decorators : +
                + @Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT)
                +
                + +
                + +
                + Returns : any + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Private + Async + retryGetTokenPrice + + +
                + + retryGetTokenPrice(tokenAddress: string, retries: number, delayMinutes: number) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1942,7 +4188,7 @@

                - Returns : any + Returns : Promise<number>
                @@ -1956,26 +4202,63 @@

                + + + + + + + + + + + + + + + +
                NameTypeOptionalDefault value
                tokenAddress + string + + No + +
                retries + number + + No + + 3 +
                delayMinutes + number No + 15 +
                - + + + subscriptionInfo + + +
                +subscriptionInfo() +
                + +
                + +
                + Returns : { payment: number; decimals: number; amount: any; calculateAmount: (billingCycle: any) => number; calculateProrated: (billingCycle: any) => any; } + +
                +
                + + + + @@ -1996,9 +4279,9 @@

                - + @@ -2061,11 +4344,28 @@

                + + + + + + + + + + + @@ -2133,9 +4433,9 @@

                - + - + + +
                + Async - handleWebhookReceiveAndFundPaymasterAndDeleteWalletAddressFromOperatorsWebhook - + subscriptionWeb3 +
                - handleWebhookReceiveAndFundPaymasterAndDeleteWalletAddressFromOperatorsWebhook(webhookEvent: WebhookEvent) + subscriptionWeb3(environment: string)
                - +
                webhookEventenvironment - WebhookEvent + string @@ -2013,7 +4296,7 @@

                - Returns : Promise<void> + Returns : unknown
                @@ -2027,26 +4310,26 @@

                - + Async - operatorAccountActivationEvent - + updateCheckout +
                - operatorAccountActivationEvent(undefined) + updateCheckout(sessionId: string, paymentStatus: string)
                - +
                NameType Optional
                sessionId + string + + No +
                paymentStatus + string + No @@ -2079,7 +4379,7 @@

                - Returns : any + Returns : unknown
                @@ -2093,26 +4393,26 @@

                - + Async - predictWallet - + updateIsActivated +
                - predictWallet(owner: string, index: number, ver: string, environment: string) + updateIsActivated(_id: ObjectId, isActivated: boolean)
                - +
                owner_id - string + ObjectId @@ -2145,9 +4445,9 @@

                indexisActivated - number + boolean @@ -2156,8 +4456,67 @@

                +
                +
                +
                +
                + Returns : Promise<any> + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + @@ -502,13 +551,14 @@

                import { HttpService } from '@nestjs/axios'
                -import { HttpException, 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
                @@ -565,13 +615,20 @@ 

                .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 || '' + e?.response?.data?.errors?.message || + e?.message || + 'Unknown error occurred' + + 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( - `${e?.response?.statusText}: ${errorReason}`, - e?.response?.status - ) + throw new HttpException(errorMessage, status) }) ) return await lastValueFrom(observable) diff --git a/documentation/injectables/SimpleStakingService.html b/documentation/injectables/SimpleStakingService.html new file mode 100644 index 00000000..5dd06fb8 --- /dev/null +++ b/documentation/injectables/SimpleStakingService.html @@ -0,0 +1,1092 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
                +
                + + +
                +
                + + + + + + + + + + + +
                +
                +

                +

                File

                +

                +

                + apps/charge-network-service/src/staking/staking-providers/simple-staking.service.ts +

                + + + + + +
                +

                Index

                +

                + + + Async + updateIsActivatedByOwnerId + + +
                + + updateIsActivatedByOwnerId(ownerId: string, isActivated: boolean) +
                + +
                + +
                + Parameters : + + + + + + + + + + - + @@ -2169,9 +4528,9 @@

                - + @@ -2240,9 +4599,9 @@

                - + - + @@ -2321,9 +4682,21 @@

                - + + + + + + + + + @@ -2392,9 +4765,9 @@

                - + - + @@ -2470,7 +4843,7 @@

                -
                import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
                +        
                import { BadRequestException, ExecutionContext, HttpException, HttpStatus, Inject, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
                 import { JwtService } from '@nestjs/jwt'
                 import { ethers } from 'ethers'
                 import { notificationsService, smartWalletsService } from '@app/common/constants/microservices.constants'
                @@ -2482,7 +4855,7 @@ 

                import { ConfigService } from '@nestjs/config' import { CreateOperatorUserDto } from '@app/accounts-service/operators/dto/create-operator-user.dto' import { OperatorWallet } from '@app/accounts-service/operators/interfaces/operator-wallet.interface' -import { operatorWalletModelString } from '@app/accounts-service/operators/operators.constants' +import { operatorWalletModelString, operatorRefreshTokenModelString, operatorInvoiceModelString, operatorCheckoutModelString, chargeBridgeModelString } from '@app/accounts-service/operators/operators.constants' import { Model, ObjectId } from 'mongoose' import { WebhookEvent } from '@app/apps-service/payments/interfaces/webhook-event.interface' import { ProjectsService } from '@app/accounts-service/projects/projects.service' @@ -2490,8 +4863,27 @@

                import { ClientProxy } from '@nestjs/microservices' import { CreateWebhookAddressesDto } from '@app/notifications-service/webhooks/dto/create-webhook-addresses.dto' import { AnalyticsService } from '@app/common/services/analytics.service' -import { HttpService } from '@nestjs/axios' -import axios from 'axios' +import axios, { AxiosRequestConfig } from 'axios' +import { User } from '@app/accounts-service/users/interfaces/user.interface' +import { OperatorProject } from '@app/accounts-service/operators/interfaces/operator-project.interface' +import { OperatorUserProjectResponse } from '@app/accounts-service/operators/interfaces/operator-user-project-response.interface' +import { OperatorRefreshToken } from '@app/accounts-service/operators/interfaces/operator-refresh-token.interface' +import { Response } from 'express' +import * as bcrypt from 'bcryptjs' +import { CreateOperatorWalletDto } from '@app/accounts-service/operators/dto/create-operator-wallet.dto' +import { OperatorInvoice } from '@app/accounts-service/operators/interfaces/operator-invoice.interface' +import { Cron, CronExpression } from '@nestjs/schedule' +import erc20Abi from '@app/network-service/common/constants/abi/Erc20.json' +import { CreateOperatorCheckoutDto } from '@app/accounts-service/operators/dto/create-operator-checkout.dto' +import { OperatorCheckout } from '@app/accounts-service/operators/interfaces/operator-checkout.interface' +import { ChargeCheckoutWebhookEvent } from '@app/accounts-service/operators/interfaces/charge-checkout-webhook-event.interface' +import { ChargeCheckoutBillingCycle, ChargeCheckoutPaymentStatus } from '@app/accounts-service/operators/interfaces/charge-checkout.interface' +import { differenceInMonths, getDate, getDaysInMonth, startOfMonth } from 'date-fns' +import { monthsInYear } from 'date-fns/constants' +import { BundlerProvider } from '@app/api-service/bundler-api/interfaces/bundler.interface' +import { CreateChargeBridgeDto } from '@app/accounts-service/operators/dto/create-charge-bridge.dto' +import { ChargeBridge } from '@app/accounts-service/operators/interfaces/charge-bridge.interface' +import TradeService from '@app/common/token/trade.service' @Injectable() export class OperatorsService { @@ -2509,7 +4901,15 @@

                @Inject(notificationsService) private readonly notificationsClient: ClientProxy, private readonly analyticsService: AnalyticsService, - private httpService: HttpService + @Inject(operatorRefreshTokenModelString) + private operatorRefreshTokenModel: Model<OperatorRefreshToken>, + @Inject(operatorInvoiceModelString) + private operatorInvoiceModel: Model<OperatorInvoice>, + @Inject(operatorCheckoutModelString) + private operatorCheckoutModel: Model<OperatorCheckout>, + @Inject(chargeBridgeModelString) + private chargeBridgeModel: Model<ChargeBridge>, + private readonly tradeService: TradeService ) { } async checkOperatorExistenceByEoaAddress (eoaAddress: string): Promise<number> { @@ -2517,14 +4917,18 @@

                return operator ? 200 : 404 } - validate (authOperatorDto: AuthOperatorDto): string { - const recoveredAddress = ethers.utils.verifyMessage(authOperatorDto.message, authOperatorDto.signature) - if (authOperatorDto.externallyOwnedAccountAddress !== recoveredAddress) { - throw new HttpException('Wallet ownership verification failed', HttpStatus.FORBIDDEN) + async validate (authOperatorDto: AuthOperatorDto, response: Response) { + try { + const recoveredAddress = ethers.utils.verifyMessage(authOperatorDto.message, authOperatorDto.signature) + if (authOperatorDto.externallyOwnedAccountAddress !== recoveredAddress) { + throw new HttpException('Wallet ownership verification failed', HttpStatus.FORBIDDEN) + } + await this.createOperatorJwtTokens(recoveredAddress, response) + response.status(200).send() + } catch (error) { + this.logger.error(`Failed to validate operator: ${error.message}`) + throw error } - return this.jwtService.sign({ - sub: recoveredAddress - }) } async getOperatorUserAndProject (auth0Id: string) { @@ -2534,6 +4938,11 @@

                throw new HttpException('User not found', HttpStatus.NOT_FOUND) } + const wallet = await this.findWalletOwner(user._id) + if (!wallet) { + throw new HttpException('Wallet not found', HttpStatus.NOT_FOUND) + } + const projectObject = await this.projectsService.findOneByOwnerId(user._id) if (!projectObject) { throw new HttpException('Project not found', HttpStatus.NOT_FOUND) @@ -2549,7 +4958,7 @@

                throw new HttpException('No active paymasters found', HttpStatus.NOT_FOUND) } - const project = { + const project: OperatorProject = { id: projectObject._id, ownerId: projectObject.ownerId, name: projectObject.name, @@ -2561,7 +4970,7 @@

                sponsorId: paymasters[0].sponsorId } - return { user, project } + return this.constructUserProjectResponse(user, project, wallet) } catch (error) { this.errorHandler(error) } @@ -2573,20 +4982,27 @@

                try { const user = await this.createUser(createOperatorUserDto, auth0Id) const projectObject = await this.createProject(user, createOperatorUserDto) - const publicKey = await this.projectsService.getPublic(projectObject._id) const secretKey = await this.createProjectSecret(projectObject) + const apiKeyInfo = await this.projectsService.getApiKeysInfo(projectObject._id) const sponsorId = await this.createPaymasters(projectObject) - const predictedWallet = await this.predictWallet(auth0Id, 0, '0_1_0', 'production') const eventData = { email: user.email, - apiKey: publicKey + apiKey: apiKeyInfo.publicKey } - await this.createOperatorWallet(user, predictedWallet) - await this.addAddressToOperatorsWebhook(predictedWallet) - await this.addAddressToTokenReceiveWebhook(predictedWallet) this.googleFormSubmit(createOperatorUserDto) this.analyticsService.trackEvent('New Operator Created', { ...eventData }, { user_id: user?.auth0Id }) - return this.constructUserProjectResponse(user, projectObject, publicKey.publicKey, secretKey, sponsorId) + const project: OperatorProject = { + id: projectObject._id, + ownerId: projectObject.ownerId, + name: projectObject.name, + description: projectObject.description, + publicKey: apiKeyInfo.publicKey, + sandboxKey: apiKeyInfo.sandboxKey, + secretPrefix: apiKeyInfo.secretPrefix, + secretLastFourChars: apiKeyInfo.secretLastFourChars, + sponsorId + } + return this.constructUserProjectResponse(user, project, undefined, secretKey) } catch (error) { this.errorHandler(error) } @@ -2624,33 +5040,80 @@

                return paymasters[0].sponsorId } - private async createOperatorWallet (user: any, predictedWallet: string) { + async createOperatorWallet (createOperatorWalletDto: CreateOperatorWalletDto, auth0Id: string) { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + const operatorWalletCreationResult = await this.operatorWalletModel.create({ ownerId: user._id, - smartWalletAddress: predictedWallet.toLowerCase() + smartWalletAddress: createOperatorWalletDto.smartWalletAddress.toLowerCase() }) if (!operatorWalletCreationResult) { throw new HttpException('Failed to create operator wallet', HttpStatus.INTERNAL_SERVER_ERROR) } + + await this.addAddressToOperatorsWebhook(operatorWalletCreationResult.smartWalletAddress) + await this.addAddressToTokenReceiveWebhook(operatorWalletCreationResult.smartWalletAddress) + + return operatorWalletCreationResult + } + + async migrateOperatorWallet (migrateOperatorWalletDto: CreateOperatorWalletDto, auth0Id: string) { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + const existingWallet = await this.operatorWalletModel.findOne({ ownerId: user._id }) + if (existingWallet.etherspotSmartWalletAddress) { + throw new HttpException('Already migrated operator wallet', HttpStatus.BAD_REQUEST) + } + + await this.operatorWalletModel.findOneAndUpdate( + { ownerId: user._id }, + { $rename: { smartWalletAddress: 'etherspotSmartWalletAddress' } } + ) + const operatorWalletResult = await this.operatorWalletModel.findOneAndUpdate( + { ownerId: user._id }, + { $set: { smartWalletAddress: migrateOperatorWalletDto.smartWalletAddress.toLowerCase() } }, + { new: true } + ) + if (!operatorWalletResult) { + throw new HttpException('Failed to migrate operator wallet', HttpStatus.INTERNAL_SERVER_ERROR) + } + + await this.addAddressToOperatorsWebhook(operatorWalletResult.smartWalletAddress) + await this.addAddressToTokenReceiveWebhook(operatorWalletResult.smartWalletAddress) + + return operatorWalletResult } - private constructUserProjectResponse (user: any, projectObject: any, publicKey: string, secretKey: string, sponsorId: string) { - // Constructs the response object from the created entities + private constructUserProjectResponse (user: User, project: OperatorProject, wallet?: OperatorWallet, secretKey?: string): OperatorUserProjectResponse { + // Constructs the response object from the entities return { user: { id: user._id, name: user.name, email: user.email, - auth0Id: user.auth0Id + auth0Id: user.auth0Id, + smartWalletAddress: wallet?.smartWalletAddress ?? '0x', + isActivated: wallet?.isActivated ?? false, + createdAt: user.createdAt, + etherspotSmartWalletAddress: wallet?.etherspotSmartWalletAddress ?? null }, project: { - id: projectObject._id, - ownerId: projectObject.ownerId, - name: projectObject.name, - description: projectObject.description, - publicKey, - secretKey, - sponsorId + id: project.id, + ownerId: project.ownerId, + name: project.name, + description: project.description, + publicKey: project.publicKey, + sandboxKey: project.sandboxKey, + secretPrefix: project.secretPrefix, + secretLastFourChars: project.secretLastFourChars, + sponsorId: project.sponsorId, + secretKey } } } @@ -2796,11 +5259,12 @@

                async getSponsoredTransactionsCount (auth0Id: string) { const user = await this.usersService.findOneByAuth0Id(auth0Id) const project = await this.projectsService.findOneByOwnerId(user._id) - const paymasters = await this.paymasterService.findActivePaymasters(project._id) - const sponsorId = paymasters?.[0]?.sponsorId + const apiKey = await this.projectsService.getApiKeysInfo(project._id) + const publicKey = apiKey.publicKey let sponsoredTransactions = 0 - if (sponsorId) { - sponsoredTransactions = await callMSFunction(this.dataLayerClient, 'sponsored-transactions-count', sponsorId) + if (publicKey) { + const startDate = startOfMonth(new Date()).toISOString() + sponsoredTransactions = await callMSFunction(this.dataLayerClient, 'sponsored-transactions-count', { apiKey: publicKey, startDate }) .catch(e => { this.logger.log(`sponsored-transactions-count failed: ${JSON.stringify(e)}`) }) @@ -2825,10 +5289,18 @@

                return this.operatorWalletModel.findOne({ smartWalletAddress: value.toLowerCase() }) } + async findAllOperatorWallets (): Promise<OperatorWallet[]> { + return this.operatorWalletModel.find() + } + async updateIsActivated (_id: ObjectId, isActivated: boolean): Promise<any> { return this.operatorWalletModel.updateOne({ _id }, { isActivated }) } + async updateIsActivatedByOwnerId (ownerId: string, isActivated: boolean): Promise<any> { + return this.operatorWalletModel.updateOne({ ownerId }, { isActivated }) + } + async getBalance (address: string, ver: string, environment: string): Promise<string> { const paymasterEnvs = this.configService.getOrThrow(`paymaster.${ver}`) const provider = new ethers.providers.JsonRpcProvider(paymasterEnvs[environment].url) @@ -2888,15 +5360,20 @@

                } async googleFormSubmit (createOperatorUserDto: CreateOperatorUserDto) { - const formActionUrl = this.configService.getOrThrow('googleOperatorFormUrl') + try { + const formActionUrl = this.configService.get('googleOperatorFormUrl') - const formData = new URLSearchParams() - formData.append('entry.1781500597', createOperatorUserDto.email) - formData.append('entry.1823923312', createOperatorUserDto.firstName) - formData.append('entry.995318623', createOperatorUserDto.lastName) - formData.append('entry.1016494914', createOperatorUserDto.name) + if (!formActionUrl) { + this.logger.warn('Google Form URL not configured, skipping form submission') + return + } + + const formData = new URLSearchParams() + formData.append('entry.1781500597', createOperatorUserDto.email) + formData.append('entry.1823923312', createOperatorUserDto.firstName) + formData.append('entry.995318623', createOperatorUserDto.lastName) + formData.append('entry.1016494914', createOperatorUserDto.name) - try { await axios.post(formActionUrl, formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' @@ -2905,11 +5382,553 @@

                this.logger.log('Submission to Google Form successful') } catch (error) { - this.logger.error('Submission to Google Form failed:', error.response ? error.response.data : error.message) - throw new HttpException( - `Error sending data to Google Form: ${error.response?.statusText || 'Unknown Error'}: ${error.response?.data?.error || ''}`, - error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR + this.logger.error('Google Form submission failed:', error.message) + } + } + + async findRefreshToken (auth0Id: string): Promise<OperatorRefreshToken> { + return this.operatorRefreshTokenModel.findOne({ auth0Id }).sort({ createdAt: -1 }) + } + + async createRefreshToken (auth0Id: string, refreshToken: string): Promise<OperatorRefreshToken> { + return this.operatorRefreshTokenModel.create({ auth0Id, refreshToken }) + } + + async markRefreshTokenAsUsed (refreshToken: string) { + return this.operatorRefreshTokenModel.updateOne( + { refreshToken, usedAt: null }, + { usedAt: new Date() } + ) + } + + async invalidateRefreshTokens (auth0Id: string) { + return this.operatorRefreshTokenModel.updateMany( + { auth0Id, invalidAt: null }, + { invalidAt: new Date() } + ) + } + + async hashRefreshToken (token: string) { + const salt = await bcrypt.genSalt() + return bcrypt.hash(token, salt) + } + + async compareRefreshToken (plainToken: string, hashedToken: string) { + return bcrypt.compare(plainToken, hashedToken) + } + + async validateRefreshToken (token: string, response: Response) { + try { + const verifiedRefreshToken = this.jwtService.verify( + token, + { + secret: this.configService.get('OPERATOR_REFRESH_JWT_SECRET') + } + ) + if (!verifiedRefreshToken) { + throw new HttpException('Refresh token verification failed', HttpStatus.UNAUTHORIZED) + } + + const refreshToken = await this.findRefreshToken(verifiedRefreshToken.sub) + if (!refreshToken) { + throw new HttpException('Refresh token does not exist', HttpStatus.NOT_FOUND) + } + + if (verifiedRefreshToken.sub !== refreshToken.auth0Id) { + throw new HttpException('Refresh token does not match', HttpStatus.UNAUTHORIZED) + } + + const currentTime = Math.floor(Date.now() / 1000) + if (currentTime > verifiedRefreshToken.exp) { + throw new HttpException('Refresh token expired', HttpStatus.UNAUTHORIZED) + } + + const hashedRefreshToken = refreshToken.refreshToken + const comparedRefreshToken = await this.compareRefreshToken(token, hashedRefreshToken) + if (!comparedRefreshToken) { + throw new HttpException('Refresh token comparison failed', HttpStatus.UNAUTHORIZED) + } + + if (refreshToken.invalidAt) { + throw new HttpException('Refresh token invalidated', HttpStatus.FORBIDDEN) + } + if (refreshToken.usedAt) { + await this.invalidateRefreshTokens(refreshToken.auth0Id) + throw new HttpException('Refresh token used', HttpStatus.FORBIDDEN) + } + + await this.markRefreshTokenAsUsed(hashedRefreshToken) + await this.createOperatorJwtTokens(refreshToken.auth0Id, response) + + response.status(200).send() + } catch (error) { + this.logger.error(`Failed to validate operator refresh token: ${error.message}`) + throw error + } + } + + async createOperatorJwtTokens (auth0Id: string, response: Response) { + const tenMinutesInSeconds = 10 * 60 + const oneDayInSeconds = 24 * 60 * 60 + const milliseconds = 1000 + + const accessToken = this.jwtService.sign( + { + sub: auth0Id + }, + { + secret: this.configService.get('SMART_WALLETS_JWT_SECRET'), + expiresIn: tenMinutesInSeconds + } + ) + const refreshToken = this.jwtService.sign( + { + sub: auth0Id + }, + { + secret: this.configService.get('OPERATOR_REFRESH_JWT_SECRET'), + expiresIn: oneDayInSeconds + } + ) + + response.cookie('operator_access_token', accessToken, { + httpOnly: true, + secure: true, + maxAge: tenMinutesInSeconds * milliseconds, + sameSite: this.configService.get('CONSOLE_DAPP_URL') ? 'lax' : 'none' + }) + response.cookie('operator_refresh_token', refreshToken, { + httpOnly: true, + secure: true, + maxAge: oneDayInSeconds * milliseconds, + sameSite: this.configService.get('CONSOLE_DAPP_URL') ? 'lax' : 'none' + }) + + const hashedRefreshToken = await this.hashRefreshToken(refreshToken) + return this.createRefreshToken(auth0Id, hashedRefreshToken) + } + + async createInvoice (ownerId: string, amount: number, currency: string, txHash: string, amountUsd?: number): Promise<OperatorInvoice> { + return this.operatorInvoiceModel.create({ ownerId, amount, currency, txHash, amountUsd }) + } + + async findInvoices (ownerId: string): Promise<OperatorInvoice[]> { + return this.operatorInvoiceModel.find({ ownerId }) + } + + async findFirstDayOfMonthInvoice (ownerId: string): Promise<OperatorInvoice> { + const currentDate = new Date() + const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1) + return this.operatorInvoiceModel.findOne({ ownerId, createdAt: { $gte: firstDayOfMonth } }) + } + + async subscriptionWeb3 (environment: string) { + const version = '0_1_0' + const paymasterEnvs = this.configService.getOrThrow(`paymaster.${version}.${environment}`) + const tokenEnvs = this.configService.getOrThrow(`token.${environment}`) + const contractAddress = tokenEnvs.wfuseContractAddress + const privateKey = this.configService.get('PAYMASTER_FUNDER_PRIVATE_KEY') + const provider = new ethers.providers.JsonRpcProvider(paymasterEnvs.url) + const wallet = new ethers.Wallet(privateKey, provider) + const contract = new ethers.Contract(contractAddress, erc20Abi, wallet) + const symbol = await contract.symbol() + return { + contractAddress, + provider, + wallet, + contract, + symbol + } + } + + subscriptionInfo () { + const payment = 50 + const decimals = 18 + const amount = ethers.utils.parseUnits(payment.toString(), decimals) + + const today = new Date() + const daysInMonth = getDaysInMonth(today) + const dayOfMonth = getDate(today) + const remainingDays = daysInMonth - dayOfMonth + 1 + const proratedFactor = remainingDays / daysInMonth + + const tiers = { + [ChargeCheckoutBillingCycle.YEARLY]: { + percentageOff: 30, + multiplier: monthsInYear + }, + [ChargeCheckoutBillingCycle.MONTHLY]: { + percentageOff: 0, + multiplier: 1 + } + } + + function calculateAmount (billingCycle) { + const { percentageOff, multiplier } = tiers[billingCycle] + const discount = payment * (percentageOff / 100) + return (payment - discount) * multiplier + } + + function calculateProrated (billingCycle) { + const calculatedAmount = calculateAmount(billingCycle) + return Math.round(payment * proratedFactor + calculatedAmount - payment) + } + + return { + payment, + decimals, + amount, + calculateAmount, + calculateProrated + } + } + + async operatorSubscriptionPaidEvent ({ id, token, amount }) { + try { + const user = await this.usersService.findOne(id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + const project = await this.projectsService.findOneByOwnerId(user._id) + if (!project) { + throw new HttpException('Project not found', HttpStatus.NOT_FOUND) + } + + const apiKey = await this.projectsService.getApiKeysInfo(project._id) + if (!apiKey) { + throw new HttpException('API Key info not found', HttpStatus.NOT_FOUND) + } + + const eventData = { + email: user.email, + apiKey: apiKey.publicKey, + token, + amount + } + this.analyticsService.trackEvent('Operator Subscription Paid', { ...eventData }, { user_id: user?.auth0Id }) + } catch (error) { + this.logger.error('Error tracking Operator Subscription Paid event:', error) + } + } + + async createSubscription (auth0Id: string): Promise<OperatorInvoice> { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + const operatorWallet = await this.findWalletOwner(user._id) + if (!operatorWallet) { + throw new HttpException('Operator wallet not found.', HttpStatus.NOT_FOUND) + } + + const { contractAddress, wallet, contract, symbol } = await this.subscriptionWeb3('production') + const { decimals, calculateProrated } = this.subscriptionInfo() + const proratedAmount = calculateProrated(ChargeCheckoutBillingCycle.MONTHLY) + + const tokenPrice = await this.tradeService.getTokenPriceByAddress(contractAddress) + if (!tokenPrice || tokenPrice <= 0) { + throw new HttpException('Token price is not available', HttpStatus.INTERNAL_SERVER_ERROR) + } + + const proratedTokenAmount = proratedAmount / tokenPrice + const amount = ethers.utils.parseUnits(proratedTokenAmount.toString(), decimals) + + const allowance = await contract.allowance(operatorWallet.smartWalletAddress, wallet.address) + this.logger.log('subscriptionAllowance', JSON.stringify({ allowance: allowance.toString(), amount: amount.toString(), proratedTokenAmount, proratedAmount, tokenPrice, smartWalletAddress: operatorWallet.smartWalletAddress, walletAddress: wallet.address, contractAddress }, null, 2)) + if (allowance.lt(amount)) { + throw new HttpException('Insufficient allowance', HttpStatus.BAD_REQUEST) + } + + const transfer = await contract.transferFrom(operatorWallet.smartWalletAddress, wallet.address, amount) + const tx = await transfer.wait() + const txHash = tx?.transactionHash + if (!txHash) { + throw new HttpException('Transaction failed', HttpStatus.BAD_REQUEST) + } + + const invoice = await this.createInvoice(user._id, proratedTokenAmount, symbol, txHash, proratedAmount) + await this.updateIsActivated(operatorWallet._id, true) + + this.operatorSubscriptionPaidEvent({ id: user._id, token: symbol, amount: proratedTokenAmount }) + + return invoice + } + + async getSubscriptions (auth0Id: string): Promise<OperatorInvoice[]> { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + return this.findInvoices(user._id) + } + + private async retryGetTokenPrice (tokenAddress: string, retries = 3, delayMinutes = 15): Promise<number> { + for (let attempt = 1; attempt <= retries; attempt++) { + const tokenPrice = await this.tradeService.getTokenPriceByAddress(tokenAddress) + if (tokenPrice && tokenPrice > 0) { + return tokenPrice + } + + this.logger.warn(`Token price fetch attempt ${attempt}/${retries} failed. Retrying in ${delayMinutes} minutes...`) + if (attempt < retries) { + await new Promise(resolve => setTimeout(resolve, delayMinutes * 60 * 1000)) + } + } + throw new Error('Failed to get token price after multiple attempts') + } + + @Cron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + async processMonthlySubscriptions () { + try { + const operatorWallets = await this.findAllOperatorWallets() + const { contractAddress, wallet, contract, symbol } = await this.subscriptionWeb3('production') + const { payment, decimals } = this.subscriptionInfo() + + const tokenPrice = await this.retryGetTokenPrice(contractAddress) + + const tokenAmount = payment / tokenPrice + const amount = ethers.utils.parseUnits(tokenAmount.toString(), decimals) + + for (const operatorWallet of operatorWallets) { + const invoice = await this.findFirstDayOfMonthInvoice(operatorWallet.ownerId) + if (invoice) { + this.logger.log(`Invoice already exists for ${operatorWallet.ownerId}`) + continue + } + + const allowance = await contract.allowance(operatorWallet.smartWalletAddress, wallet.address) + if (allowance.lt(amount)) { + this.logger.log(`Insufficient allowance for ${operatorWallet.ownerId}`) + await this.updateIsActivated(operatorWallet._id, false) + continue + } + + const balance = await contract.balanceOf(operatorWallet.smartWalletAddress) + if (balance.lt(amount)) { + this.logger.log(`Insufficient balance for ${operatorWallet.ownerId}`) + await this.updateIsActivated(operatorWallet._id, false) + continue + } + + const transfer = await contract.transferFrom(operatorWallet.smartWalletAddress, wallet.address, amount) + const tx = await transfer.wait() + const txHash = tx?.transactionHash + if (!txHash) { + this.logger.log(`Transaction failed for ${operatorWallet.ownerId}`) + await this.updateIsActivated(operatorWallet._id, false) + continue + } + + await this.createInvoice(operatorWallet.ownerId, tokenAmount, symbol, txHash, payment) + await this.updateIsActivated(operatorWallet._id, true) + + this.operatorSubscriptionPaidEvent({ id: operatorWallet.ownerId, token: symbol, amount: tokenAmount }) + } + } catch (error) { + this.logger.error('Failed to process monthly subscriptions', error) + throw error + } + } + + async findCheckout (sessionId: string) { + return this.operatorCheckoutModel.findOne({ sessionId }) + } + + async findLastPaidCheckout (ownerId: string) { + return this.operatorCheckoutModel.findOne({ ownerId, paymentStatus: ChargeCheckoutPaymentStatus.PAID }).sort({ createdAt: -1 }) + } + + async updateCheckout (sessionId: string, paymentStatus: string) { + return this.operatorCheckoutModel.updateOne({ sessionId }, { paymentStatus }) + } + + async checkout (auth0Id: string, createOperatorCheckoutDto: CreateOperatorCheckoutDto) { + try { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + const chargePaymentsApiUrl = this.configService.get('CHARGE_PAYMENTS_API_URL') + const chargePaymentsApiKey = this.configService.get('CHARGE_PAYMENTS_API_KEY') + const accountsUrl = this.configService.get('AUTH0_AUDIENCE') + const expiresIn = 12000 + + const { calculateProrated } = this.subscriptionInfo() + const proratedAmount = calculateProrated(createOperatorCheckoutDto.billingCycle) + + const chargeResponse = await axios.post( + `${chargePaymentsApiUrl}/payments/checkout/sessions?apiKey=${chargePaymentsApiKey}`, + { + successUrl: createOperatorCheckoutDto.successUrl, + cancelUrl: createOperatorCheckoutDto.cancelUrl, + webhookUrl: `${accountsUrl}/accounts/v1/operators/checkout/webhook`, + expiresIn, + lineItems: [ + { + currency: 'usd', + unitAmount: proratedAmount.toString(), + quantity: '1', + productData: { + name: 'Console Operator' + } + } + ] + } + ) + + const checkout = await this.operatorCheckoutModel.create({ + ownerId: user._id, + sessionId: chargeResponse.data.id, + billingCycle: createOperatorCheckoutDto.billingCycle, + amount: proratedAmount, + ...chargeResponse.data + }) + return checkout.url + } catch (error) { + this.logger.error(`Failed to checkout: ${error.message}`) + throw error + } + } + + async getCheckoutSessions (auth0Id: string) { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + return this.operatorCheckoutModel.find( + { ownerId: user._id }, + { + billingCycle: 1, + status: 1, + paymentStatus: 1, + createdAt: 1, + updatedAt: 1, + amount: 1 + } + ) + } + + async handleCheckoutWebhook (webhookEvent: ChargeCheckoutWebhookEvent) { + const checkout = await this.findCheckout(webhookEvent.sessionId) + if (!checkout) { + throw new HttpException('Checkout not found', HttpStatus.NOT_FOUND) + } + + const isPaid = webhookEvent.paymentStatus === ChargeCheckoutPaymentStatus.PAID + if (isPaid) { + await this.updateIsActivatedByOwnerId(checkout.ownerId, true) + } + + await this.updateCheckout(webhookEvent.sessionId, webhookEvent.paymentStatus) + } + + async processMonthlyBilling () { + const operatorWallets = await this.findAllOperatorWallets() + for (const operatorWallet of operatorWallets) { + const checkout = await this.findLastPaidCheckout(operatorWallet.ownerId) + if (!checkout) { + continue + } + + const monthsSinceCreation = differenceInMonths(new Date(), checkout.createdAt) + if ( + checkout.billingCycle === ChargeCheckoutBillingCycle.YEARLY && + monthsSinceCreation < monthsInYear + ) { + continue + } + + const gracePeriod = 1 + if (monthsSinceCreation <= gracePeriod) { + continue + } + + await this.updateIsActivatedByOwnerId(checkout.ownerId, false) + } + } + + async isOperatorSponsoredQuotaExceeded (context: ExecutionContext, requestConfig: AxiosRequestConfig) { + const request = context.switchToHttp().getRequest() + const bundlerProvider = request.query?.provider ?? BundlerProvider.ETHERSPOT + if (bundlerProvider !== BundlerProvider.PIMLICO) { + return false + } + + const paymaster = requestConfig.data?.params?.[0]?.paymaster + if (!paymaster) { + return false + } + + const operatorUser = await callMSFunction(this.dataLayerClient, 'get-operator-by-api-key', request.query.apiKey) + .catch(e => { + this.logger.log(`get-operator-by-api-key failed: ${JSON.stringify(e)}`) + }) + if (!operatorUser) { + return true + } + const { operator } = operatorUser + if (!operator) { + return true + } + if (operator.isActivated) { + return false + } + + const freePlanLimit = 1000 + const startDate = startOfMonth(new Date()).toISOString() + const sponsoredTransactions = await callMSFunction(this.dataLayerClient, 'sponsored-transactions-count', { apiKey: request.query.apiKey, startDate }) + .catch(e => { + this.logger.log(`sponsored-transactions-count failed: ${JSON.stringify(e)}`) + }) + if (sponsoredTransactions > freePlanLimit) { + return true + } + + return false + } + + async createChargeBridge (auth0Id: string, createChargeBridgeDto: CreateChargeBridgeDto) { + try { + const user = await this.usersService.findOneByAuth0Id(auth0Id) + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND) + } + + const operatorWallet = await this.findWalletOwner(user._id) + if (!operatorWallet) { + throw new HttpException('Operator wallet not found.', HttpStatus.NOT_FOUND) + } + + const chargePaymentsApiUrl = this.configService.get('CHARGE_PAYMENTS_API_URL') + const chargePaymentsApiKey = this.configService.get('CHARGE_PAYMENTS_API_KEY') + const chargeBridgeResponse = await axios.post( + `${chargePaymentsApiUrl}/payments/bridge/create-new?apiKey=${chargePaymentsApiKey}`, + { + chainId: createChargeBridgeDto.chainId, + token: 'FUSE', + amount: createChargeBridgeDto.amount, + destinationWallet: operatorWallet.smartWalletAddress, + receiveToken: 'WFUSE' + } ) + + const chargeBridge = await this.chargeBridgeModel.create({ + ...chargeBridgeResponse.data, + ownerId: user._id + }) + return { + walletAddress: chargeBridge.walletAddress, + startTime: chargeBridge.startTime, + endTime: chargeBridge.endTime + } + } catch (error) { + this.logger.error(`Failed to create charge bridge: ${error.message}`) + throw error } } } diff --git a/documentation/injectables/PaymasterApiService.html b/documentation/injectables/PaymasterApiService.html index d5bc77af..015c5065 100644 --- a/documentation/injectables/PaymasterApiService.html +++ b/documentation/injectables/PaymasterApiService.html @@ -263,8 +263,8 @@

                @@ -382,8 +382,8 @@

                @@ -468,8 +468,8 @@

                @@ -587,8 +587,8 @@

                @@ -658,8 +658,8 @@

                @@ -847,8 +847,8 @@

                @@ -915,8 +915,8 @@

                @@ -1086,7 +1086,6 @@

                const paymasterAndDataForEstimateUserOpGasCall = this.buildPaymasterAndData(paymasterAddress, validUntil, validAfter, sponsorId, signatureForEstimateUserOpGasCall) op.paymasterAndData = paymasterAndDataForEstimateUserOpGasCall - const gases: GasDetails = await this.estimateUserOpGas(op, env, paymasterInfo.entrypointAddress) const actualVerificationGasLimit = Math.max(parseInt(gases.verificationGasLimit), parseInt(minVerificationGasLimit)).toString() @@ -1175,10 +1174,16 @@

                ) .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) @@ -1223,8 +1228,7 @@

                private prepareUrl (environment) { if (isEmpty(environment)) throw new InternalServerErrorException('Bundler environment is missing') - const config = this.configService.get(`bundler.${environment}`) - + const config = this.configService.get(`bundler.etherspot.${environment}`) if (config.url) { return config.url } else { diff --git a/documentation/injectables/RelayAPIService.html b/documentation/injectables/RelayAPIService.html index b8601075..f47e86fc 100644 --- a/documentation/injectables/RelayAPIService.html +++ b/documentation/injectables/RelayAPIService.html @@ -68,6 +68,22 @@

                File

                Index

                NameTypeOptional
                verownerId string
                environmentisActivated - string + boolean @@ -2186,7 +4545,7 @@

                - Returns : Promise<string> + Returns : Promise<any>
                @@ -2200,26 +4559,26 @@

                - + Async - updateIsActivated - + validate +
                - updateIsActivated(_id: ObjectId, isActivated: boolean) + validate(authOperatorDto: AuthOperatorDto, response: Response)
                - +
                _idauthOperatorDto - ObjectId + AuthOperatorDto @@ -2252,9 +4611,9 @@

                isActivatedresponse - boolean + Response @@ -2269,7 +4628,7 @@

                - Returns : Promise<any> + Returns : any
                @@ -2283,24 +4642,26 @@

                - + - validate - + Private + validateInput +
                -validate(authOperatorDto: AuthOperatorDto) + + validateInput(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string)
                - +
                authOperatorDtocreateOperatorUserDto - AuthOperatorDto + CreateOperatorUserDto + + No +
                auth0Id + string @@ -2338,7 +4711,7 @@

                - Returns : string + Returns : void
                @@ -2352,26 +4725,26 @@

                - + - Private - validateInput - + Async + validateRefreshToken +
                - validateInput(createOperatorUserDto: CreateOperatorUserDto, auth0Id: string) + validateRefreshToken(token: string, response: Response)
                - +
                createOperatorUserDtotoken - CreateOperatorUserDto + string @@ -2404,9 +4777,9 @@

                auth0Idresponse - string + Response @@ -2421,7 +4794,7 @@

                - Returns : void + Returns : any
                @@ -2457,7 +4830,7 @@

                - +
                - +
                - +
                - +
                - +
                - +
                - +
                - +
                + + + + + + @@ -208,8 +224,8 @@

                @@ -275,8 +291,8 @@

                @@ -352,8 +368,8 @@

                @@ -423,8 +439,8 @@

                @@ -466,6 +482,39 @@

                +

                +
                +
                Properties
                +
                +
                  +
                • + Private + Readonly + logger +
                • +
                +
                @@ -131,7 +147,7 @@

                Constructor

                - +
                - +
                - +
                - +
                - +
                + +
                + +

                + Properties +

                + + + + + + + + + + + + +
                + + + Private + Readonly + logger + + +
                + Default value : new Logger(RelayAPIService.name) +
                + +
                @@ -490,7 +539,7 @@

                - +
                + + + + + + + + + + + + + + + + + + + + + + + + + + +
                +
                Properties
                +
                + +
                +
                Methods
                +
                + +
                +
                Accessors
                +
                + +
                + + +
                +

                Constructor

                + + + + + + + + + + + + + +
                +constructor(provider: JsonRpcProvider, configService: ConfigService, tradeService: TradeService, graphService: GraphService) +
                + +
                +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                provider + JsonRpcProvider + + No +
                configService + ConfigService + + No +
                tradeService + TradeService + + No +
                graphService + GraphService + + No +
                +
                +
                +
                + +
                + +

                + Methods +

                + + + + + + + + + + + + + + + + + + + +
                + + + Private + getPoolId + + +
                + + getPoolId(tokenAddress: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                tokenAddress + string + + No +
                +
                +
                +
                +
                + Returns : number + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Private + getPoolsConfig + + +
                + + getPoolsConfig() +
                + +
                + +
                + Returns : Record<string, PoolConfig> + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + stake + + +
                +stake(stakeDto: StakeDto) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                stakeDto + StakeDto + + No +
                +
                +
                +
                +
                + Returns : any + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + stakedToken + + +
                + + stakedToken(accountAddress: string, stakingOption: StakingOption) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                accountAddress + string + + No +
                stakingOption + StakingOption + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + stakingApr + + +
                + + stakingApr(stakingOption: StakingOption) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                stakingOption + StakingOption + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + tvl + + +
                + + tvl(stakingOption: StakingOption) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                stakingOption + StakingOption + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + unStake + + +
                +unStake(undefined: UnstakeDto) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                + UnstakeDto + + No +
                +
                +
                +
                +
                + Returns : any + +
                +
                + +
                +
                +
                +
                + +

                + Properties +

                + + + + + + + + + + + + + + +
                + + + Private + Readonly + logger + + +
                + Default value : new Logger(SimpleStakingService.name) +
                + +
                + + + + + + + + + + + + + + +
                + + + Private + Readonly + SECONDS_IN_MONTH + + +
                + Default value : 60 * 60 * 24 * 30 +
                + +
                +
                + +
                +

                + Accessors +

                + + + + + + + + + + + + + + +
                + + address +
                + getaddress() +
                + +
                + + + + + + + + + + + + + + +
                + + masterChefV3Interface +
                + getmasterChefV3Interface() +
                + +
                +
                +

                + + +
                +
                import { Injectable, Logger } from '@nestjs/common'
                +import { ConfigService } from '@nestjs/config'
                +import {
                +  Contract,
                +  InjectEthersProvider,
                +  Interface,
                +  JsonRpcProvider,
                +  formatUnits,
                +  parseUnits
                +} from 'nestjs-ethers'
                +import { StakingOption, StakingProvider } from '../interfaces'
                +import { UnstakeDto } from '../dto/unstake.dto'
                +import MasterChefV3ABI from '@app/network-service/common/constants/abi/MasterChefV3.json'
                +import TradeService from '@app/common/token/trade.service'
                +import { StakeDto } from '../dto/stake.dto'
                +import GraphService from '../graph.service'
                +import { GET_SIMPLE_STAKING_POOL_DATA as getSimpleStakingPoolData } from '@app/network-service/common/constants/graph-queries/masterchef-v3'
                +import { PoolConfig } from '@app/network-service/common/constants/simple-staking-config'
                +
                +@Injectable()
                +export default class SimpleStakingService implements StakingProvider {
                +  private readonly logger = new Logger(SimpleStakingService.name)
                +
                +  constructor (
                +    @InjectEthersProvider('regular-node')
                +    private readonly provider: JsonRpcProvider,
                +    private readonly configService: ConfigService,
                +    private readonly tradeService: TradeService,
                +    private readonly graphService: GraphService
                +  ) {}
                +
                +  private readonly SECONDS_IN_MONTH = 60 * 60 * 24 * 30
                +
                +  get address () {
                +    return this.configService.get('masterChefV3Address')
                +  }
                +
                +  get masterChefV3Interface () {
                +    return new Interface(MasterChefV3ABI)
                +  }
                +
                +  private getPoolId (tokenAddress: string): number {
                +    const poolConfig = this.getPoolsConfig()
                +    const config = poolConfig[tokenAddress.toLowerCase()]
                +    return config.poolId
                +  }
                +
                +  private getPoolsConfig (): Record<string, PoolConfig> {
                +    return this.configService.get('simpleStakingConfig')
                +  }
                +
                +  stake (stakeDto: StakeDto) {
                +    const poolsConfig = this.getPoolsConfig()
                +    const poolConfig = poolsConfig[stakeDto.tokenAddress.toLowerCase()]
                +
                +    return this.masterChefV3Interface.encodeFunctionData('deposit', [
                +      poolConfig.poolId,
                +      parseUnits(stakeDto.tokenAmount, poolConfig.decimals)
                +    ])
                +  }
                +
                +  unStake ({ tokenAddress, tokenAmount }: UnstakeDto) {
                +    const poolsConfig = this.getPoolsConfig()
                +    const poolConfig = poolsConfig[tokenAddress.toLowerCase()]
                +
                +    return this.masterChefV3Interface.encodeFunctionData('withdraw', [
                +      poolConfig.poolId,
                +      parseUnits(tokenAmount, poolConfig.decimals)
                +    ])
                +  }
                +
                +  async stakedToken (accountAddress: string, stakingOption: StakingOption) {
                +    try {
                +      const masterChefContract = new Contract(
                +        this.address,
                +        MasterChefV3ABI,
                +        this.provider
                +      )
                +
                +      const poolId = this.getPoolId(stakingOption.tokenAddress)
                +
                +      const [userInfo, pendingTokens] = await Promise.all([
                +        masterChefContract.userInfo(poolId, accountAddress),
                +        masterChefContract.pendingTokens(poolId, accountAddress)
                +      ])
                +
                +      const stakedAmount = Number(formatUnits(userInfo.amount, stakingOption.decimals))
                +
                +      const tokenPrice = await this.tradeService.getTokenPriceByAddress(
                +        stakingOption.tokenAddress
                +      )
                +      const stakedAmountUSD = stakedAmount * tokenPrice
                +
                +      const pendingBonusTokenAmount = Number(
                +        formatUnits(pendingTokens[3], stakingOption.decimals)
                +      )
                +
                +      const earnedAmountUSD = pendingBonusTokenAmount * tokenPrice
                +      const stakingApr = await this.stakingApr(stakingOption)
                +
                +      return {
                +        ...stakingOption,
                +        stakedAmount,
                +        stakedAmountUSD,
                +        earnedAmountUSD,
                +        stakingApr
                +      }
                +    } catch (error) {
                +      this.logger.error(`stakedToken error: ${error}`)
                +      throw error
                +    }
                +  }
                +
                +  async stakingApr (stakingOption: StakingOption) {
                +    try {
                +      const poolId = this.getPoolId(stakingOption.tokenAddress)
                +
                +      type SimpleStakingPool = {
                +        pool: {
                +          balance: string
                +          rewarder: {
                +            tokenPerSec: string
                +          }
                +        }
                +      }
                +
                +      const result = await this.graphService
                +        .getMasterChefV3Client()
                +        .request<SimpleStakingPool>(getSimpleStakingPoolData, { poolId: poolId.toString() })
                +
                +      const pool = result.pool
                +      const tokenPerSec = formatUnits(pool.rewarder.tokenPerSec, stakingOption.decimals)
                +      const balance = formatUnits(pool.balance, stakingOption.decimals)
                +
                +      const rewardsPerYear =
                +        parseFloat(tokenPerSec) * this.SECONDS_IN_MONTH * 12
                +
                +      const totalStaked = parseFloat(balance)
                +
                +      const tokenPrice = await this.tradeService.getTokenPriceByAddress(
                +        stakingOption.tokenAddress
                +      )
                +
                +      const rewardsPerYearUSD = rewardsPerYear * tokenPrice
                +      const totalStakedUSD = totalStaked * tokenPrice
                +
                +      return (rewardsPerYearUSD / totalStakedUSD) * 100
                +    } catch (error) {
                +      this.logger.error(`stakingApr error: ${error}`)
                +      return 0
                +    }
                +  }
                +
                +  async tvl (stakingOption: StakingOption) {
                +    try {
                +      const poolsConfig = this.getPoolsConfig()
                +      const poolConfig = poolsConfig[stakingOption.tokenAddress.toLowerCase()]
                +
                +      const masterChefContract = new Contract(
                +        this.address,
                +        MasterChefV3ABI,
                +        this.provider
                +      )
                +
                +      const poolInfo = await masterChefContract.poolInfo(poolConfig.poolId)
                +
                +      const lpToken = new Contract(
                +        poolInfo.lpToken,
                +        ['function balanceOf(address) view returns (uint256)'],
                +        this.provider
                +      )
                +
                +      const balance = await lpToken.balanceOf(this.address)
                +
                +      const tokenPrice = await this.tradeService.getTokenPriceByAddress(
                +        poolInfo.lpToken
                +      )
                +
                +      const formattedBalance = formatUnits(balance, poolConfig.decimals)
                +      return Number(formattedBalance) * tokenPrice
                +    } catch (error) {
                +      this.logger.error(`tvl error: ${error}`)
                +    }
                +  }
                +}
                +
                +
                + +

                + + + + + + + + + + + + +

                +
                +

                results matching ""

                +
                  +
                  +
                  +

                  No results matching ""

                  +
                  +
                  +

                  + +

                  +

                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/injectables/SmartWalletsAAService.html b/documentation/injectables/SmartWalletsAAService.html index f9dd7d36..d6ddb52b 100644 --- a/documentation/injectables/SmartWalletsAAService.html +++ b/documentation/injectables/SmartWalletsAAService.html @@ -130,7 +130,7 @@

                  Constructor

                  - + @@ -231,8 +231,8 @@

                  - + @@ -303,8 +303,8 @@

                  - + @@ -375,8 +375,8 @@

                  - + @@ -450,7 +450,7 @@

                  - + @@ -464,7 +464,8 @@

                  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'
                  @@ -522,7 +523,15 @@ 

                  } } 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({ + error: err.message || 'Authentication error', + status: HttpStatus.BAD_REQUEST + }) } } diff --git a/documentation/injectables/SmartWalletsLegacyService.html b/documentation/injectables/SmartWalletsLegacyService.html index 2569b5ea..661bdfbc 100644 --- a/documentation/injectables/SmartWalletsLegacyService.html +++ b/documentation/injectables/SmartWalletsLegacyService.html @@ -161,7 +161,7 @@

                  Constructor

                  - + @@ -274,8 +274,8 @@

                  - + @@ -345,8 +345,8 @@

                  - + @@ -416,8 +416,8 @@

                  - + @@ -455,8 +455,8 @@

                  - + @@ -526,8 +526,8 @@

                  - + @@ -597,8 +597,8 @@

                  - + @@ -636,8 +636,8 @@

                  - + @@ -711,7 +711,7 @@

                  - + @@ -740,7 +740,7 @@

                  - + @@ -762,7 +762,7 @@

                  - + @@ -775,14 +775,15 @@

                  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'
                   import { smartWalletString } from '@app/smart-wallets-service/smart-wallets/smart-wallets.constants'
                   import { SmartWallet, SmartWalletService } from '@app/smart-wallets-service/smart-wallets/interfaces/smart-wallets.interface'
                  -import { generateSalt, generateTransactionId } from 'apps/charge-smart-wallets-service/src/common/utils/helper-functions'
                  -import RelayAPIService from 'apps/charge-smart-wallets-service/src/common/services/relay-api.service'
                  +import { generateSalt, generateTransactionId } from '@app/smart-wallets-service/common/utils/helper-functions'
                  +import RelayAPIService from '@app/smart-wallets-service/common/services/relay-api.service'
                   import { RelayDto } from '@app/smart-wallets-service/smart-wallets/dto/relay.dto'
                   import { ISmartWalletUser } from '@app/common/interfaces/smart-wallet.interface'
                   import { CentClient } from 'cent.js'
                  @@ -827,48 +828,73 @@ 

                  } } catch (err) { this.logger.error(`An error occurred during Smart Wallets Auth. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + error: err.message || 'Authentication error', + status: HttpStatus.BAD_REQUEST + }) } } async getWallet (smartWalletUser: ISmartWalletUser) { - const { ownerAddress } = smartWalletUser - const smartWallet = await this.smartWalletModel.findOne({ ownerAddress }) - if (!smartWallet) { - throw new Error('Not found') - } - if (!smartWallet.isContractDeployed) { - const transactionId = generateTransactionId(smartWallet.salt) - const walletModules = this.sharedAddresses.walletModules - this.relayAPIService.createWallet({ - v2: true, - salt: smartWallet.salt, - transactionId, - smartWalletUser, - owner: ownerAddress, + try { + const { ownerAddress } = smartWalletUser + this.logger.log(`Fetching Smart Wallet for owner address: ${ownerAddress}`) + const smartWallet = await this.smartWalletModel.findOne({ ownerAddress }) + if (!smartWallet) { + this.logger.warn(`Smart Wallet not found for owner address: ${ownerAddress}`) + 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...`) + const transactionId = generateTransactionId(smartWallet.salt) + const walletModules = this.sharedAddresses.walletModules + this.relayAPIService.createWallet({ + v2: true, + salt: smartWallet.salt, + transactionId, + smartWalletUser, + owner: ownerAddress, + walletModules, + walletFactoryAddress: this.sharedAddresses.WalletFactory + }).catch(err => { + const errorMessage = `Retry wallet creation failed: ${err}` + this.logger.error(errorMessage) + }) + } + + const { + smartWalletAddress, walletModules, - walletFactoryAddress: this.sharedAddresses.WalletFactory - }).catch(err => { - const errorMessage = `Retry wallet creation failed: ${err}` - this.logger.error(errorMessage) - }) - } + networks, + version, + paddedVersion + } = smartWallet - const { - smartWalletAddress, - walletModules, - networks, - version, - paddedVersion - } = smartWallet - - return { - smartWalletAddress, - walletModules, - networks, - version, - paddedVersion, - ownerAddress + this.logger.log(`Smart Wallet found for owner address: ${ownerAddress}`) + return { + smartWalletAddress, + walletModules, + networks, + version, + paddedVersion, + ownerAddress + } + } catch (err) { + this.logger.error(`An error occurred during fetching Legacy Smart Wallet. ${err}`) + // 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({ + error: err.message || 'Error fetching smart wallet', + status: HttpStatus.BAD_REQUEST + }) } } @@ -876,7 +902,10 @@

                  try { const { ownerAddress } = smartWalletUser if (await this.smartWalletModel.findOne({ ownerAddress })) { - throw new Error('Owner address already has a deployed smart wallet') + throw new RpcException({ + error: 'Owner address already has a deployed smart wallet', + status: HttpStatus.CONFLICT + }) } const salt = generateSalt() const transactionId = generateTransactionId(salt) @@ -897,26 +926,57 @@

                  } } catch (err) { this.logger.error(`An error occurred during Smart Wallets Creation. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + error: err.message || 'Error creating smart wallet', + status: HttpStatus.BAD_REQUEST + }) } } async relay (relayDto: RelayDto) { try { const transactionId = generateTransactionId(relayDto.data) - await this.centClient.subscribe({ channel: `transaction:#${transactionId}`, user: relayDto.ownerAddress }) - this.relayAPIService.relay({ - v2: true, + const wsUrl = this.wsUrl + + if (!wsUrl) { + throw new Error('WebSocket URL is not configured') + } + + // Non-blocking subscription with error handling + const subscribePromise = this.centClient.subscribe({ + channel: `transaction:#${transactionId}`, + user: relayDto.ownerAddress + }) + + if (subscribePromise && typeof subscribePromise.catch === 'function') { + subscribePromise.catch(err => { + this.logger.error(`Centrifugo subscription failed: ${err}`) + }) + } + + // Fire-and-forget relay call with error handling (v2 flag MUST be set AFTER spread) + const relayPromise = this.relayAPIService.relay({ + ...relayDto, transactionId, - ...relayDto + v2: true }) + + if (relayPromise && typeof relayPromise.catch === 'function') { + relayPromise.catch(err => { + this.logger.error(`Relay API call failed: ${err}`) + }) + } + return { - connectionUrl: this.wsUrl, + connectionUrl: wsUrl, transactionId } } catch (err) { this.logger.error(`An error occurred during relay execution. ${err}`) - throw new HttpException(err.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + error: err.message || 'Relay execution error', + status: HttpStatus.BAD_REQUEST + }) } } @@ -929,7 +989,10 @@

                  } } catch (error) { this.logger.error(`An error occurred during fetching historical txs. ${error}`) - throw new HttpException(error.message, HttpStatus.BAD_REQUEST) + throw new RpcException({ + error: error.message || 'Error fetching historical transactions', + status: HttpStatus.BAD_REQUEST + }) } } diff --git a/documentation/injectables/StakingAPIService.html b/documentation/injectables/StakingAPIService.html index 24727898..9e6721ed 100644 --- a/documentation/injectables/StakingAPIService.html +++ b/documentation/injectables/StakingAPIService.html @@ -85,14 +85,30 @@

                  Methods
                  Async stakedTokens +
                • + Async + stakedTokensV2 +
                • +
                • + Async + stakeV2 +
                • Async stakingOptions
                • +
                • + Async + stakingOptionsV2 +
                • Async unStake
                • +
                • + Async + unStakeV2 +
                @@ -181,8 +197,8 @@

                - + @@ -252,8 +268,79 @@

                - + + + + + + + + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                accountAddress + string + + No +
                +
                +
                +
                +
                + Returns : Promise<UserStakedTokens> + +
                +
                + +
                + + + + + + + + + + + + + + + + @@ -301,6 +388,77 @@

                + + + Async + stakedTokensV2 + + +
                + + stakedTokensV2(accountAddress: string) +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + stakeV2 + + +
                + + stakeV2(stakeDto: StakeDto) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                stakeDto + StakeDto + + No +
                +
                +
                +
                +
                + Returns : Promise<any> + +
                +
                + +
                +
                @@ -329,6 +487,45 @@

                + + + + +
                + + +
                + + + + + + + + + + + + + + + + + + + + + + +
                + + + Async + stakingOptionsV2 + + +
                + + stakingOptionsV2() +
                + +
                @@ -362,8 +559,79 @@

                - + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                unstakeDto + UnstakeDto + + No +
                +
                +
                +
                +
                + Returns : Promise<any> + +
                +
                + +
                +
                + + + + + + + + + + + + @@ -435,17 +703,33 @@

                return callMSFunction(this.stakingClient, 'staking_options', {}) } + async stakingOptionsV2 (): Promise<Array<StakingOption>> { + return callMSFunction(this.stakingClient, 'staking_options_v2', {}) + } + async stake (stakeDto: StakeDto): Promise<any> { return callMSFunction(this.stakingClient, 'stake', stakeDto) } + async stakeV2 (stakeDto: StakeDto): Promise<any> { + return callMSFunction(this.stakingClient, 'stake_v2', stakeDto) + } + async unStake (unstakeDto: UnstakeDto): Promise<any> { return callMSFunction(this.stakingClient, 'unstake', unstakeDto) } + async unStakeV2 (unstakeDto: UnstakeDto): Promise<any> { + return callMSFunction(this.stakingClient, 'unstake_v2', unstakeDto) + } + async stakedTokens (accountAddress: string): Promise<UserStakedTokens> { return callMSFunction(this.stakingClient, 'staked_tokens', accountAddress) } + + async stakedTokensV2 (accountAddress: string): Promise<UserStakedTokens> { + return callMSFunction(this.stakingClient, 'staked_tokens_v2', accountAddress) + } } diff --git a/documentation/injectables/StakingService.html b/documentation/injectables/StakingService.html index 268c5756..3baf711c 100644 --- a/documentation/injectables/StakingService.html +++ b/documentation/injectables/StakingService.html @@ -97,6 +97,10 @@

                Methods
                PrivategetStakingOption +
              • + Private + getStakingOptionV2 +
              • Private getStakingProvider @@ -109,14 +113,30 @@
                Methods
                Async stakedTokens
              • +
              • + Async + stakedTokensV2 +
              • +
              • + Async + stakeV2 +
              • Async stakingOptions
              • +
              • + Async + stakingOptionsV2 +
              • Async unStake
              • +
              • + Async + unStakeV2 +
              • @@ -136,6 +156,9 @@
                Accessors
              • stakingOptionsConfig
              • +
              • + stakingOptionsV2Config +
              • @@ -149,12 +172,12 @@

                Constructor

                @@ -194,6 +217,18 @@

                Constructor

                No + + + + + + + + @@ -243,8 +278,79 @@

                + + + + + + + +
                + + + Async + unStakeV2 + + +
                + + unStakeV2(unstakeDto: UnstakeDto) +
                +
                -constructor(voltBarService: VoltBarService, fuseLiquidStakingService: FuseLiquidStakingService, configService: ConfigService) +constructor(voltBarService: VoltBarService, fuseLiquidStakingService: FuseLiquidStakingService, simpleStakingService: SimpleStakingService, configService: ConfigService)
                - +
                simpleStakingService + SimpleStakingService + + No +
                configService
                - + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                tokenAddress + string + + No +
                +
                +
                +
                +
                + Returns : any + +
                +
                + +
                +
                + + + + + + + + + + + + @@ -314,8 +420,8 @@

                @@ -385,8 +491,8 @@

                @@ -456,8 +562,79 @@

                + + + + + + + +
                + + + Private + getStakingOptionV2 + + +
                + + getStakingOptionV2(tokenAddress: string) +
                +
                - +
                - +
                - + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                accountAddress + string + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + @@ -505,6 +682,77 @@

                + + + Async + stakedTokensV2 + + +
                + + stakedTokensV2(accountAddress: string) +
                +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + stakeV2 + + +
                + + stakeV2(stakeDto: StakeDto) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                stakeDto + StakeDto + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                @@ -527,8 +775,47 @@

                + + + + + + + +
                - + +
                + +
                + Returns : unknown + +
                +
                + + + + + + + + + + + + @@ -566,8 +853,79 @@

                + + + + + + + +
                + + + Async + stakingOptionsV2 + + +
                + + stakingOptionsV2() +
                +
                - + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                unStakeDto + UnstakeDto + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                + + + + + + + + + + + + @@ -641,7 +999,7 @@

                @@ -670,7 +1028,29 @@

                + + + +
                + + + Async + unStakeV2 + + +
                + + unStakeV2(unStakeDto: UnstakeDto) +
                +
                - +
                - + +
                + + + + + + + + + + + @@ -684,12 +1064,22 @@

                import { Injectable, Logger } from '@nestjs/common'
                 import { UnstakeDto } from '@app/network-service/staking/dto/unstake.dto'
                 import { StakeDto } from '@app/network-service/staking/dto/stake.dto'
                -import { StakingOption, StakingProvider } from '@app/network-service/staking/interfaces'
                +import {
                +  StakingOption,
                +  StakingProvider
                +} from '@app/network-service/staking/interfaces'
                 import VoltBarService from '@app/network-service/staking/staking-providers/volt-bar.service'
                 import { sumBy } from 'lodash'
                -import { fuseLiquidStakingId, voltBarId } from '@app/network-service/common/constants'
                +import {
                +  fuseLiquidStakingId,
                +  usdcOnStargateSimpleStakingId,
                +  voltBarId,
                +  wethOnStargateSimpleStakingId
                +} from '@app/network-service/common/constants'
                 import { ConfigService } from '@nestjs/config'
                 import FuseLiquidStakingService from '@app/network-service/staking/staking-providers/fuse-liquid-staking.service'
                +import SimpleStakingService from '@app/network-service/staking/staking-providers/simple-staking.service'
                +import { isLowercaseEqual } from '@app/common/utils/lowercase_equal'
                 
                 @Injectable()
                 export class StakingService {
                @@ -697,13 +1087,18 @@ 

                constructor ( private readonly voltBarService: VoltBarService, private readonly fuseLiquidStakingService: FuseLiquidStakingService, + private readonly simpleStakingService: SimpleStakingService, private readonly configService: ConfigService - ) { } + ) {} get stakingOptionsConfig () { return this.configService.get('stakingOptions') as Array<StakingOption> } + get stakingOptionsV2Config () { + return this.configService.get('stakingOptionsV2') as Array<StakingOption> + } + async stakingOptions () { const stakingOptionsData: Array<StakingOption> = [] @@ -722,6 +1117,24 @@

                return stakingOptionsData } + async stakingOptionsV2 () { + const stakingOptionsData: Array<StakingOption> = [] + + for (const stakingOption of this.stakingOptionsV2Config) { + const stakingProvider = this.getStakingProvider(stakingOption) + const stakingApr = await stakingProvider.stakingApr(stakingOption) + const tvl = await stakingProvider.tvl(stakingOption) + + stakingOptionsData.push({ + ...stakingOption, + stakingApr, + tvl + }) + } + + return stakingOptionsData + } + async stake (stakeDto: StakeDto) { const stakingOption = this.getStakingOption(stakeDto.tokenAddress) const stakingProvider = this.getStakingProvider(stakingOption) @@ -732,6 +1145,16 @@

                } } + async stakeV2 (stakeDto: StakeDto) { + const stakingOption = this.getStakingOptionV2(stakeDto.tokenAddress) + const stakingProvider = this.getStakingProvider(stakingOption) + + return { + contractAddress: stakingProvider.address, + encodedABI: stakingProvider.stake(stakeDto) + } + } + async unStake (unStakeDto: UnstakeDto) { const stakingOption = this.getStakingOption(unStakeDto.tokenAddress) const stakingProvider = this.getStakingProvider(stakingOption) @@ -742,6 +1165,16 @@

                } } + async unStakeV2 (unStakeDto: UnstakeDto) { + const stakingOption = this.getStakingOptionV2(unStakeDto.tokenAddress) + const stakingProvider = this.getStakingProvider(stakingOption) + + return { + contractAddress: stakingProvider.address, + encodedABI: stakingProvider.unStake(unStakeDto) + } + } + async stakedTokens (accountAddress: string) { const stakedTokens = [] @@ -765,15 +1198,62 @@

                } } + async stakedTokensV2 (accountAddress: string) { + const stakedTokens = [] + + try { + for (const stakingOption of this.stakingOptionsV2Config) { + const stakingProvider = this.getStakingProvider(stakingOption) + + const stakedToken = await stakingProvider.stakedToken( + accountAddress, + stakingOption + ) + + if (stakedToken.stakedAmount > 0) { + stakedTokens.push(stakedToken) + } + } + + return { + totalStakedAmountUSD: sumBy(stakedTokens, 'stakedAmountUSD'), + totalEarnedAmountUSD: sumBy(stakedTokens, 'earnedAmountUSD'), + stakedTokens + } + } catch (error) { + this.logger.error('Error fetching staked tokens', error) + } + } + private getStakingOption (tokenAddress: string) { - return this.stakingOptionsConfig.find(stakingOption => stakingOption.tokenAddress === tokenAddress) + return this.stakingOptionsConfig.find( + (stakingOption) => { + return isLowercaseEqual(stakingOption.tokenAddress, tokenAddress) + } + ) + } + + private getStakingOptionV2 (tokenAddress: string) { + return this.stakingOptionsV2Config.find( + (stakingOption) => { + return isLowercaseEqual(stakingOption.tokenAddress, tokenAddress) + } + ) } private getStakingProvider (stakingOption: StakingOption): StakingProvider { - if (stakingOption.stakingProviderId === voltBarId) { + const stakingProviderId = stakingOption.stakingProviderId + + const isSimpleStaking = + stakingProviderId === usdcOnStargateSimpleStakingId || + stakingProviderId === wethOnStargateSimpleStakingId + + if (stakingProviderId === voltBarId) { return this.voltBarService - } else if (stakingOption.stakingProviderId === fuseLiquidStakingId) { + } else if (stakingProviderId === fuseLiquidStakingId) { return this.fuseLiquidStakingService + } else if (isSimpleStaking) { + return this.simpleStakingService } } } diff --git a/documentation/injectables/TokenAddressMapper.html b/documentation/injectables/TokenAddressMapper.html index ab99746a..ebec6e32 100644 --- a/documentation/injectables/TokenAddressMapper.html +++ b/documentation/injectables/TokenAddressMapper.html @@ -77,6 +77,9 @@

                Methods

                + + stakingOptionsV2Config +
                + getstakingOptionsV2Config() +
                +
                +
                +

                Constructor

                + + + + + + + + + + + + + +
                +constructor(configService: ConfigService) +
                + +
                +
                + Parameters : + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                configService + ConfigService + + No +
                +
                +
                +

                Methods

                + + + + + + + + + + + + + + + + + + + +
                + + + getOriginalTokenAddress + + +
                +getOriginalTokenAddress(mappedTokenAddress: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                mappedTokenAddress + string + + No +
                +
                +
                +
                +
                + Returns : string + +
                +
                + +
                +
                @@ -118,8 +238,8 @@

                @@ -176,13 +296,33 @@

                import { Injectable } from '@nestjs/common'
                 import { NATIVE_FUSE_ADDRESS } from '@app/notifications-service/common/constants/addresses'
                 import { get } from 'lodash'
                +import { ConfigService } from '@nestjs/config'
                +import { isLowercaseEqual } from '@app/common/utils/lowercase_equal'
                 
                 @Injectable()
                 export class TokenAddressMapper {
                +  constructor (private readonly configService: ConfigService) {}
                +
                   getTokenAddress (tokenAddress: string): string {
                -    return get({
                -      [NATIVE_FUSE_ADDRESS.toLowerCase()]: '0x0BE9e53fd7EDaC9F859882AfdDa116645287C629'.toLowerCase()
                -    }, tokenAddress.toLowerCase(), tokenAddress.toLowerCase())
                +    const wrappedFuseAddress = this.configService.get('wfuseAddress')
                +
                +    return get(
                +      {
                +        [NATIVE_FUSE_ADDRESS.toLowerCase()]: wrappedFuseAddress.toLowerCase()
                +      },
                +      tokenAddress.toLowerCase(),
                +      tokenAddress.toLowerCase()
                +    )
                +  }
                +
                +  getOriginalTokenAddress (mappedTokenAddress: string): string {
                +    const wrappedFuseAddress = this.configService.get('wfuseAddress')
                +
                +    if (isLowercaseEqual(mappedTokenAddress, wrappedFuseAddress)) {
                +      return NATIVE_FUSE_ADDRESS.toLowerCase()
                +    }
                +
                +    return mappedTokenAddress.toLowerCase()
                   }
                 }
                 
                diff --git a/documentation/injectables/TokenPriceService.html b/documentation/injectables/TokenPriceService.html index 342146cf..67998d3a 100644 --- a/documentation/injectables/TokenPriceService.html +++ b/documentation/injectables/TokenPriceService.html @@ -68,6 +68,27 @@

                File

                Index

                - +
                + + + + + + @@ -116,12 +151,12 @@

                Constructor

                @@ -161,6 +196,18 @@

                Constructor

                No + + + + + + + + @@ -222,8 +269,8 @@

                @@ -283,6 +330,77 @@

                +
                Properties
                +
                + +
                @@ -81,11 +102,20 @@
                Methods
                Private calculatePercentChange +
              • + Async + getMultipleTokenPrices +
              • Private Async getPreviousTokenPrice
              • +
              • + Private + Async + getPriceFromClients +
              • Async getTokenData @@ -98,6 +128,11 @@
                Methods
                Async getTokenPriceChange
              • +
              • + Private + Async + getXVoltPrice +
              • -constructor(voltageV2Client: VoltageV2Client, voltageV3Client: VoltageV3Client, blocksClient: BlocksClient, tokenAddressMapper: TokenAddressMapper) +constructor(voltageV2Client: VoltageV2Client, voltageV3Client: VoltageV3Client, voltBarClient: VoltBarClient, blocksClient: BlocksClient, tokenAddressMapper: TokenAddressMapper)
                - +
                voltBarClient + VoltBarClient + + No +
                blocksClient
                - +
                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getMultipleTokenPrices + + +
                + + getMultipleTokenPrices(multipleTokenPricesDto: MultipleTokenPricesDto) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                multipleTokenPricesDto + MultipleTokenPricesDto + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                @@ -306,8 +424,8 @@

                @@ -355,6 +473,78 @@

                - +
                + + + + + + + + + + + + + + + + + + + +
                + + + Private + Async + getPriceFromClients + + +
                + + getPriceFromClients(address: string) +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                address + string + + No +
                +
                +
                +
                +
                + Returns : Promise<string> + +
                +
                + +
                +
                @@ -377,8 +567,8 @@

                @@ -460,8 +650,8 @@

                @@ -531,8 +721,8 @@

                @@ -580,47 +770,239 @@

                - +
                - +
                - +
                + + + + + + + + + + + + + + + + + + + +
                + + + Private + Async + getXVoltPrice + + +
                + + getXVoltPrice() +
                + +
                + +
                + Returns : Promise<string> + +
                +
                +
                +
                + +

                + Properties +

                + + + + + + + + + + + + + + +
                + + + Private + Readonly + logger + + +
                + Default value : new Logger(TokenPriceService.name) +
                + +
                + + + + + + + + + + + + + + + + + +
                + + + Private + Readonly + XVOLT_ADDRESS + + +
                + Type : string + +
                + Default value : '0x97a6e78c9208c21afaDa67e7E61d7ad27688eFd1' +
                + +
                -
                import { BlocksClient } from '@app/network-service/voltage-dex/services/blocks-client.service'
                -import { Injectable } from '@nestjs/common'
                +        
                import { VoltBarClient } from '@app/network-service/voltage-dex/services/volt-bar-client.service'
                +import { BlocksClient } from '@app/network-service/voltage-dex/services/blocks-client.service'
                +import { Injectable, Logger } from '@nestjs/common'
                 import { TokenAddressMapper } from '@app/network-service/voltage-dex/services/token-address-mapper.service'
                 import { TokenPriceDto } from '@app/network-service/voltage-dex/dto/token-price.dto'
                 import { VoltageV2Client } from '@app/network-service/voltage-dex/services/voltage-v2-client.service'
                 import { VoltageV3Client } from '@app/network-service/voltage-dex/services/voltage-v3-client.service'
                 import dayjs from '@app/common/utils/dayjs'
                +import { MultipleTokenPricesDto } from '../dto/multiple-token-prices.dto'
                +import { TokenPrices } from '../types'
                 
                 @Injectable()
                 export class TokenPriceService {
                +  private readonly logger = new Logger(TokenPriceService.name)
                +
                +  private readonly XVOLT_ADDRESS = '0x97a6e78c9208c21afaDa67e7E61d7ad27688eFd1'
                +
                   constructor (
                     private voltageV2Client: VoltageV2Client,
                     private voltageV3Client: VoltageV3Client,
                +    private voltBarClient: VoltBarClient,
                     private blocksClient: BlocksClient,
                     private tokenAddressMapper: TokenAddressMapper
                   ) {}
                 
                   async getTokenPrice (tokenPriceDto: TokenPriceDto): Promise<string> {
                -    const address = this.tokenAddressMapper.getTokenAddress(tokenPriceDto.tokenAddress)
                +    const { tokenAddress } = tokenPriceDto
                +    this.logger.log(`Getting token price for ${tokenAddress}`)
                +
                +    if (tokenAddress.toLowerCase() === this.XVOLT_ADDRESS.toLowerCase()) {
                +      return this.getXVoltPrice()
                +    }
                +
                +    try {
                +      const address = this.tokenAddressMapper.getTokenAddress(tokenAddress)
                +      const price = await this.getPriceFromClients(address)
                +
                +      this.logger.log(`Got token price for ${tokenAddress}`)
                +      return price
                +    } catch (error) {
                +      this.logger.error(`Error getting token price for ${tokenAddress}`, error)
                +      return '0'
                +    }
                +  }
                +
                +  async getMultipleTokenPrices (multipleTokenPricesDto: MultipleTokenPricesDto) {
                +    try {
                +      const tokenAddresses = multipleTokenPricesDto.tokenAddresses.map(
                +        address => this.tokenAddressMapper.getTokenAddress(address)
                +      )
                +
                +      const prices = await this.voltageV3Client.getMultipleTokenPrices(
                +        tokenAddresses
                +      )
                +
                +      const tokensWithoutPrices: TokenPrices = {}
                +      const tokensWithPrices: TokenPrices = {}
                +
                +      for (const [address, price] of Object.entries(prices)) {
                +        if (price === null) {
                +          tokensWithoutPrices[address] = price
                +        } else {
                +          tokensWithPrices[address] = price
                +        }
                +      }
                +
                +      const tokensWithoutPricesArray = Object.keys(tokensWithoutPrices)
                +      const v2Prices = await this.voltageV2Client.getMultipleTokenPrices(tokensWithoutPricesArray)
                +
                +      const allPrices = { ...v2Prices, ...tokensWithPrices }
                +
                +      // Revert the token mapper
                +      const revertedPrices = Object.fromEntries(
                +        Object.entries(allPrices).map(([address, price]) => [
                +          this.tokenAddressMapper.getOriginalTokenAddress(address),
                +          price
                +        ])
                +      )
                +
                +      return revertedPrices
                +    } catch (err) {
                +      this.logger.error('Error getting multiple token prices', err)
                +      return {}
                +    }
                +  }
                +
                +  private async getXVoltPrice (): Promise<string> {
                +    const [ratio, voltPrice] = await Promise.all([
                +      this.voltBarClient.getRatio(),
                +      this.getTokenPrice({ tokenAddress: '0x34ef2cc892a88415e9f02b91bfa9c91fc0be6bd4' })
                +    ])
                 
                +    return (parseFloat(ratio) * parseFloat(voltPrice)).toString()
                +  }
                +
                +  private async getPriceFromClients (address: string): Promise<string> {
                     const v3Price = await this.voltageV3Client.getTokenPrice(address)
                     if (v3Price) {
                +      this.logger.log('Got token price from V3')
                       return v3Price
                     }
                 
                -    return this.voltageV2Client.getTokenPrice(address)
                +    const v2Price = await this.voltageV2Client.getTokenPrice(address)
                +    this.logger.log('Got token price from V2')
                +    return v2Price
                   }
                 
                   async getTokenPriceChange (tokenPriceDto: TokenPriceDto) {
                -    const [currentPrice, previousPrice] = await Promise.all([
                -      this.getTokenPrice(tokenPriceDto),
                -      this.getPreviousTokenPrice(tokenPriceDto)
                -    ])
                -    const priceChange = this.calculatePercentChange(currentPrice, previousPrice)
                -    return { priceChange: priceChange.toString(), currentPrice, previousPrice }
                +    this.logger.log(`Getting token price change for ${tokenPriceDto.tokenAddress}`)
                +    try {
                +      const [currentPrice, previousPrice] = await Promise.all([
                +        this.getTokenPrice(tokenPriceDto),
                +        this.getPreviousTokenPrice(tokenPriceDto)
                +      ])
                +      const priceChange = this.calculatePercentChange(currentPrice, previousPrice)
                +      this.logger.log(`Got token price change for ${tokenPriceDto.tokenAddress}`)
                +      return { priceChange: priceChange.toString(), currentPrice, previousPrice }
                +    } catch (error) {
                +      this.logger.error(`Error getting token price change for ${tokenPriceDto.tokenAddress}`, error)
                +      return { priceChange: '0', currentPrice: '0', previousPrice: '0' }
                +    }
                   }
                 
                   private async getPreviousTokenPrice (tokenPriceDto: TokenPriceDto): Promise<string> {
                diff --git a/documentation/injectables/TokenStatsService.html b/documentation/injectables/TokenStatsService.html
                index 6ea6772e..6fb015ff 100644
                --- a/documentation/injectables/TokenStatsService.html
                +++ b/documentation/injectables/TokenStatsService.html
                @@ -68,6 +68,22 @@ 

                File

                Index

                + + + + + + @@ -222,8 +238,8 @@

                @@ -306,8 +322,8 @@

                @@ -389,8 +405,8 @@

                @@ -460,8 +476,8 @@

                @@ -531,8 +547,8 @@

                @@ -602,8 +618,8 @@

                @@ -685,8 +701,8 @@

                @@ -756,8 +772,8 @@

                @@ -815,6 +831,39 @@

                +

                +
                +
                Properties
                +
                +
                  +
                • + Private + Readonly + logger +
                • +
                +
                @@ -133,7 +149,7 @@

                Constructor

                - +
                - +
                - +
                - +
                - +
                - +
                - +
                - +
                - +
                + +
                + +

                + Properties +

                + + + + + + + + + + + + +
                + + + Private + Readonly + logger + + +
                + Default value : new Logger(TokenStatsService.name) +
                + +
                @@ -825,7 +874,7 @@

                import { Stat, TokenStat } from '@app/network-service/voltage-dex/interfaces'
                 
                -import { Injectable } from '@nestjs/common'
                +import { Injectable, Logger } from '@nestjs/common'
                 import { TokenAddressMapper } from '@app/network-service/voltage-dex/services/token-address-mapper.service'
                 import { TokenHistoricalStatisticsDto } from '@app/network-service/voltage-dex/dto/token-stats.dto'
                 import { TokenPriceChangeIntervalDto } from '@app/network-service/voltage-dex/dto/token-price-change-interval.dto'
                @@ -836,6 +885,8 @@ 

                @Injectable() export class TokenStatsService { + private readonly logger = new Logger(TokenStatsService.name) + constructor ( private voltageV2Client: VoltageV2Client, private voltageV3Client: VoltageV3Client, @@ -843,29 +894,45 @@

                ) {} async getTokenStats (dto: TokenHistoricalStatisticsDto) { - const address = this.tokenAddressMapper.getTokenAddress(dto.tokenAddress) - const stats = await this.voltageV3Client.getTokenStats(address, dto.limit ?? 30) - return this.mapTokenStats(stats, dto.tokenAddress) + this.logger.log(`Getting token stats for ${dto.tokenAddress}`) + try { + const address = this.tokenAddressMapper.getTokenAddress(dto.tokenAddress) + const stats = await this.voltageV3Client.getTokenStats(address, dto.limit ?? 30) + const mappedStats = this.mapTokenStats(stats, dto.tokenAddress) + this.logger.log(`Got token stats for ${dto.tokenAddress}`) + return mappedStats + } catch (error) { + this.logger.error(`Error getting token stats for ${dto.tokenAddress}`, error) + return [] + } } async getTokenPriceChangeInterval (dto: TokenPriceChangeIntervalDto) { - const currentTime = dayjs.utc() - const windowSize: any = dto.timeFrame.toLowerCase() - const time = currentTime.subtract(1, windowSize).startOf('hour').unix() - const secondsInTimeFrame = currentTime.unix() - time - const numberOfDays = Math.ceil(secondsInTimeFrame / (24 * 60 * 60)) + this.logger.log(`Getting token price change interval for ${dto.tokenAddress}`) + try { + const currentTime = dayjs.utc() + const windowSize: any = dto.timeFrame.toLowerCase() + const time = currentTime.subtract(1, windowSize).startOf('hour').unix() + const secondsInTimeFrame = currentTime.unix() - time + const numberOfDays = Math.ceil(secondsInTimeFrame / (24 * 60 * 60)) + + const address = this.tokenAddressMapper.getTokenAddress(dto.tokenAddress) + const [v2Token, v3Token] = await this.fetchToken(numberOfDays, address) + + if (!v2Token && !v3Token) { + return [] + } - const address = this.tokenAddressMapper.getTokenAddress(dto.tokenAddress) - const [v2Token, v3Token] = await this.fetchToken(numberOfDays, address) + const tokenDayData = this.selectBestTokenData(v2Token, v3Token) + const parsedTokenDayData = this.parseTokenDayData(tokenDayData) + const priceChangeHistory = this.formatPriceChangeHistory(parsedTokenDayData) + this.logger.log(`Got token price change interval for ${dto.tokenAddress}`) - if (!v2Token && !v3Token) { + return priceChangeHistory + } catch (error) { + this.logger.error(`Error getting token price change interval for ${dto.tokenAddress}`, error) return [] } - - const tokenDayData = this.selectBestTokenData(v2Token, v3Token) - const parsedTokenDayData = this.parseTokenDayData(tokenDayData) - - return this.formatPriceChangeHistory(parsedTokenDayData) } private mapTokenStats (stats: any, tokenAddress: string) { diff --git a/documentation/injectables/TradeApiService.html b/documentation/injectables/TradeApiService.html index b4acdfcb..3eae6b39 100644 --- a/documentation/injectables/TradeApiService.html +++ b/documentation/injectables/TradeApiService.html @@ -77,6 +77,10 @@

                Methods
                @@ -243,27 +284,34 @@

                - + Private Async - fetchAndApplyMetadata - + fetchMetadataWithTimeout + - fetchAndApplyMetadata(collectibles: any[]) + fetchMetadataWithTimeout(uri: string) + + + Decorators : +
                + @logPerformance('UnmarshalService::fetchMetadataWithTimeout')
                + + - + @@ -284,9 +332,9 @@

                - collectibles + uri - any[] + string @@ -301,7 +349,7 @@

                - Returns : any + Returns : unknown
                @@ -315,27 +363,26 @@

                - + Private - Async - fetchCollectionSymbol - + generateCacheKey + - fetchCollectionSymbol(contractAddress: string) + generateCacheKey(parts: string[]) - + @@ -356,9 +403,9 @@

                - contractAddress + parts - string + string[] @@ -373,7 +420,7 @@

                - Returns : Promise<string> + Returns : string
                @@ -405,8 +452,8 @@

                - + @@ -485,11 +532,18 @@

                + + + Decorators : +
                + @logPerformance('UnmarshalService::getERC20TokenBalances')
                + + - + @@ -556,11 +610,18 @@

                + + + Decorators : +
                + @logPerformance('UnmarshalService::getERC721TokenBalances')
                + + - + @@ -654,8 +715,8 @@

                - + @@ -707,27 +768,27 @@

                - + Private Async - transformCollectible - + processAsset + - transformCollectible(asset: any) + processAsset(asset: any) - + @@ -775,6 +836,85 @@

                + + + + + + + + + + + + + + + + + + + + + + +
                + + + Private + Async + transformCollectibles + + +
                + + transformCollectibles(assets: any[]) +
                + Decorators : +
                + @logPerformance('UnmarshalService::transformCollectibles')
                +
                + +
                + +
                + Parameters : + + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                assets + any[] + + No +
                +
                +
                +
                +
                + Returns : unknown + +
                +
                + +
                +
                @@ -797,8 +937,8 @@

                @@ -868,8 +1008,8 @@

                @@ -923,6 +1063,76 @@

                Properties

                +
                - +
                - +
                + + + + + + + + + + + + + + + + +
                + + + Private + Readonly + CACHE_TTL + + +
                + Type : number + +
                + Default value : 60000 +
                + +
                + + + + + + + + + + + + + + + + + +
                + + + Private + Readonly + IPFS_GATEWAYS + + +
                + Type : [] + +
                + Default value : [ + 'https://ipfs.io/ipfs/', + 'https://cloudflare-ipfs.com/ipfs/', + 'https://unmarshal.mypinata.cloud/ipfs/' + ] +
                + +
                @@ -943,7 +1153,106 @@

                + + + + +
                - + +
                + + + + + + + + + + + + + + + + + +
                + + + Private + Readonly + MAX_RETRIES + + +
                + Type : number + +
                + Default value : 3 +
                + +
                + + + + + + + + + + + + + + + + + +
                + + + Private + Readonly + METADATA_TIMEOUT + + +
                + Type : number + +
                + Default value : 20000 +
                + +
                + + + + + + + + + + + + + @@ -972,7 +1281,7 @@

                @@ -994,7 +1303,7 @@

                @@ -1005,25 +1314,44 @@

                -
                import { Injectable, Logger } from '@nestjs/common'
                +        
                import { Inject, Injectable, Logger } from '@nestjs/common'
                 import { lastValueFrom, map } from 'rxjs'
                +import { Cache } from 'cache-manager'
                +import fetch from 'node-fetch'
                +import { isEmpty } from 'lodash'
                 
                 import { BalanceService } from '@app/network-service/balances/interfaces/balances.interface'
                 import { ConfigService } from '@nestjs/config'
                 import { HttpService } from '@nestjs/axios'
                 import { TokenService } from '@app/smart-wallets-service/common/services/token.service'
                -import fetch from 'node-fetch'
                -import { isEmpty } from 'lodash'
                +import { CACHE_MANAGER } from '@nestjs/cache-manager'
                +import { logPerformance } from '@app/notifications-service/common/decorators/log-performance.decorator'
                +
                +interface NFTMetadata {
                +  image?: string;
                +  description?: string;
                +  name?: string;
                +}
                 
                 @Injectable()
                 export class UnmarshalService implements BalanceService {
                   private readonly logger = new Logger(UnmarshalService.name)
                +  private readonly CACHE_TTL = 60000 // 1 minute
                +  private readonly METADATA_TIMEOUT = 20000
                +  private readonly MAX_RETRIES = 3
                +  private readonly RETRY_DELAY = 1000
                +  private readonly IPFS_GATEWAYS = [
                +    'https://ipfs.io/ipfs/',
                +    'https://cloudflare-ipfs.com/ipfs/',
                +    'https://unmarshal.mypinata.cloud/ipfs/'
                +  ]
                 
                   constructor (
                     private readonly tokenService: TokenService,
                     private readonly httpService: HttpService,
                -    private readonly configService: ConfigService
                -  ) {}
                +    private readonly configService: ConfigService,
                +    @Inject(CACHE_MANAGER) private cacheManager: Cache
                +  ) { }
                 
                   get unmarshalBaseUrl () {
                     return this.configService.get('unmarshal.baseUrl')
                @@ -1033,6 +1361,7 @@ 

                return this.configService.get('unmarshal.apiKey') } + @logPerformance('UnmarshalService::getERC20TokenBalances') async getERC20TokenBalances (address: string) { const uri = `${this.unmarshalBaseUrl}/v2/fuse/address/${address}/assets?includeLowVolume=true&auth_key=${this.unmarshalApiKey}` const observable = this.httpService @@ -1042,49 +1371,77 @@

                return this.transformToExplorerFormat(unmarshalData) } + @logPerformance('UnmarshalService::getERC721TokenBalances') async getERC721TokenBalances (address: string, limit?: number, cursor?: string) { - let uri = `${this.unmarshalBaseUrl}/v3/fuse/address/${address}/nft-assets?includeLowVolume=true&auth_key=${this.unmarshalApiKey}` - - const pageSize = Math.min(limit || 100, 100) // Ensure we don't exceed Unmarshal's limit - uri += `&pageSize=${pageSize}` - - if (cursor) { - const decodedCursor = Buffer.from(cursor, 'base64').toString('ascii') - uri += `&offset=${decodedCursor}` + const requestCacheKey = this.generateCacheKey([ + 'nft_request', + address.toLowerCase(), + (limit || 100).toString(), + cursor || '0' + ]) + const cachedRequest = await this.cacheManager.get(requestCacheKey) + + if (cachedRequest) { + return cachedRequest } - const observable = this.httpService - .get(uri) - .pipe(map(res => res.data)) - const unmarshalData = await lastValueFrom(observable) - - const transformedCollectibles = isEmpty(unmarshalData.nft_assets) - ? [] - : await Promise.all( - unmarshalData.nft_assets.map(asset => this.transformCollectible(asset)) - ) + let retries = 0 + while (retries < this.MAX_RETRIES) { + try { + let uri = `${this.unmarshalBaseUrl}/v3/fuse/address/${address}/nft-assets?includeLowVolume=true&auth_key=${this.unmarshalApiKey}` + const pageSize = Math.min(limit || 100, 100) + uri += `&pageSize=${pageSize}` - const data = { - data: { - account: { - id: address, - address, - collectibles: transformedCollectibles + if (cursor) { + const decodedCursor = Buffer.from(cursor, 'base64').toString('ascii') + uri += `&offset=${decodedCursor}` } - } - } - if (!isEmpty(transformedCollectibles)) { - await this.fetchAndApplyMetadata(data.data.account.collectibles) - } + const unmarshalData = await lastValueFrom( + this.httpService.get(uri, { + timeout: this.METADATA_TIMEOUT, + validateStatus: status => status < 500 + }).pipe(map(res => res.data)) + ) + + if (!unmarshalData || !unmarshalData.nft_assets) { + const emptyResponse = { + nextCursor: null, + data: { account: { id: address, address, collectibles: [] } } + } + await this.cacheManager.set(requestCacheKey, emptyResponse, this.CACHE_TTL) + return emptyResponse + } - const nextCursor = unmarshalData.next_offset - ? Buffer.from(unmarshalData.next_offset + '').toString('base64') - : null + const transformedCollectibles = isEmpty(unmarshalData.nft_assets) + ? [] + : await this.transformCollectibles(unmarshalData.nft_assets) + + const result = { + nextCursor: unmarshalData.next_offset + ? Buffer.from(unmarshalData.next_offset + '').toString('base64') + : null, + data: { + account: { + id: address, + address, + collectibles: transformedCollectibles + } + } + } - return { - nextCursor, - ...data + // Cache the full response + await this.cacheManager.set(requestCacheKey, result, this.CACHE_TTL) + return result + } catch (error) { + retries++ + if (retries === this.MAX_RETRIES) { + this.logger.error(`Failed to fetch NFTs from Unmarshal after ${this.MAX_RETRIES} retries: ${error.message}`) + throw error + } + this.logger.warn(`Retry ${retries}/${this.MAX_RETRIES} for address ${address}`) + await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY)) + } } } @@ -1115,70 +1472,148 @@

                } } - private async fetchAndApplyMetadata (collectibles: any[]) { - const fetchPromises = collectibles.map(async (collectible) => { - if (!collectible.descriptorUri) { - return - } + @logPerformance('UnmarshalService::transformCollectibles') + private async transformCollectibles (assets: any[]) { + const batchSize = 10 + const batches = [] - try { - if (collectible.descriptorUri.endsWith('.json')) { - const response = await fetch(collectible.descriptorUri) - const metadata = await response.json() - collectible.imageURL = `https://ipfs.io/ipfs/${metadata.image.split('://')[1]}` - } else if (collectible.descriptorUri.startsWith('ipfs://')) { - const ipfsHash = collectible.descriptorUri.split('://')[1] - const response = await fetch(`https://unmarshal.mypinata.cloud/ipfs/${ipfsHash}`) - const metadata = await response.json() - if (metadata.image) { - collectible.imageURL = metadata.image.startsWith('ipfs://') - ? `https://ipfs.io/ipfs/${metadata.image.split('://')[1]}` - : metadata.image - } - } - } catch (error) { - this.logger.error(`Error fetching metadata: ${error.message}`) - } - }) + for (let i = 0; i < assets.length; i += batchSize) { + batches.push(assets.slice(i, i + batchSize)) + } + + const results = [] + for (const batch of batches) { + const batchResults = await Promise.all( + batch.map(asset => this.processAsset(asset)) + ) + results.push(...batchResults) + } - await Promise.all(fetchPromises) + return results } - private async transformCollectible (asset: any) { + private async processAsset (asset: any) { const descriptorUri = this.transformDescriptorUri(asset.external_link) const id = this.generateId(asset.asset_contract, asset.token_id) - const collectionSymbol = await this.fetchCollectionSymbol(asset.asset_contract) + const symbolCacheKey = this.generateCacheKey(['collection_symbol', asset.asset_contract]) + let collectionSymbol = await this.cacheManager.get(symbolCacheKey) + + if (!collectionSymbol) { + collectionSymbol = asset.asset_contract_ticker_symbol || '' + if (!collectionSymbol) { + try { + const tokenDetails = await this.tokenService.fetchTokenDetails(asset.asset_contract) + collectionSymbol = tokenDetails.symbol + await this.cacheManager.set(symbolCacheKey, collectionSymbol, this.CACHE_TTL) + } catch (error) { + this.logger.error(`Error fetching token symbol: ${error.message}`) + collectionSymbol = '' + } + } + } - return { + const collectible = { collection: { collectionAddress: asset.asset_contract, collectionName: asset.asset_contract_name, collectionSymbol }, created: asset.minted_at.toString(), - creator: { - id: asset.creator - }, + creator: { id: asset.creator }, description: asset.description, descriptorUri, id, - imageURL: descriptorUri.includes('nft.voltage.finance') ? descriptorUri : asset.issuer_specific_data.image_url, + imageURL: descriptorUri?.includes('nft.voltage.finance') + ? descriptorUri + : asset.issuer_specific_data.image_url, name: asset.issuer_specific_data.name, - owner: { - id: asset.owner - }, + owner: { id: asset.owner }, tokenId: asset.token_id } + + if (descriptorUri && !collectible.imageURL) { + const metadataCacheKey = this.generateCacheKey(['nft_metadata', descriptorUri]) + const cachedMetadata = await this.cacheManager.get<NFTMetadata>(metadataCacheKey) + + if (cachedMetadata) { + collectible.imageURL = cachedMetadata.image || '' + collectible.description = cachedMetadata.description || '' + } else { + try { + const metadata = await this.fetchMetadataWithTimeout(descriptorUri) + if (metadata) { + collectible.imageURL = metadata.image?.startsWith('ipfs://') + ? `https://ipfs.io/ipfs/${metadata.image.split('://')[1]}` + : metadata.image || '' + collectible.description = metadata.description || '' + await this.cacheManager.set(metadataCacheKey, metadata, this.CACHE_TTL) + } + } catch (error) { + this.logger.error(`Error fetching metadata: ${error.message}`) + } + } + } + + return collectible } - private async fetchCollectionSymbol (contractAddress: string): Promise<string> { + @logPerformance('UnmarshalService::fetchMetadataWithTimeout') + private async fetchMetadataWithTimeout (uri: string) { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), this.METADATA_TIMEOUT) + try { - const tokenDetails = await this.tokenService.fetchTokenDetails(contractAddress) - return tokenDetails.symbol - } catch (error) { - this.logger.error(`Error fetching token symbol: ${error.message}`) - return '' + if (uri.includes('/ipfs/')) { + const cid = uri.split('/ipfs/')[1] + for (const gateway of this.IPFS_GATEWAYS) { + try { + const response = await fetch(`${gateway}${cid}`, { + signal: controller.signal, + headers: { + Accept: 'application/json' + } + }) + + if (!response.ok) { + continue + } + + const text = await response.text() + try { + const metadata = JSON.parse(text) + return metadata + } catch { + continue + } + } catch { + continue + } + } + throw new Error('All IPFS gateways failed') + } + + // For non-IPFS URIs + const response = await fetch(uri, { + signal: controller.signal, + headers: { + Accept: 'application/json' + } + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const text = await response.text() + try { + const metadata = JSON.parse(text) + return metadata + } catch (e) { + throw new Error(`Invalid JSON: ${text.substring(0, 50)}...`) + } + } finally { + clearTimeout(timeoutId) } } @@ -1197,8 +1632,13 @@

                } private generateId (contractAddress: string, tokenId: string): string { - const hexTokenId = parseInt(tokenId).toString(16).padStart(4, '0') - return `${contractAddress}-0x${hexTokenId}` + try { + const tokenNum = BigInt(tokenId) + const hexTokenId = tokenNum.toString(16).padStart(4, '0') + return `${contractAddress}-0x${hexTokenId}` + } catch { + return `${contractAddress}-invalid-${tokenId}` + } } private isBase64 (str: string): boolean { @@ -1212,6 +1652,10 @@

                return false } } + + private generateCacheKey (parts: string[]): string { + return parts.map(p => encodeURIComponent(p)).join(':') + } }

                diff --git a/documentation/injectables/UserOpEventsScannerService.html b/documentation/injectables/UserOpEventsScannerService.html index 2bb61163..52bfdce1 100644 --- a/documentation/injectables/UserOpEventsScannerService.html +++ b/documentation/injectables/UserOpEventsScannerService.html @@ -637,9 +637,9 @@

                try { await this.processEvent(log) } catch (error) { - // this.logger.error('Failed to process log:') - // this.logger.error({ log }) - // this.logger.error(error) + this.logger.error('Failed to process log:') + this.logger.error({ log }) + this.logger.error(error) } } } @@ -666,10 +666,10 @@

                } try { callMSFunction(this.dataLayerClient, 'update-user-op', eventData).catch((error) => { - // this.logger.error(`Failed to call update-user-op: ${error.message}`) + this.logger.error(`Failed to call update-user-op: ${error.message}`) }) } catch (error) { - // this.logger.error(`Failed to call update-user-op: ${error.message}`) + this.logger.error(`Failed to call update-user-op: ${error.message}`) } } } diff --git a/documentation/injectables/VoltBarClient.html b/documentation/injectables/VoltBarClient.html new file mode 100644 index 00000000..3a51e976 --- /dev/null +++ b/documentation/injectables/VoltBarClient.html @@ -0,0 +1,289 @@ + + + + + + fusebox-backend documentation + + + + + + + + + + + + + +
                +
                + + +
                +
                + + + + + + + + + + + +
                +
                +

                +

                File

                +

                +

                + apps/charge-network-service/src/voltage-dex/services/volt-bar-client.service.ts +

                + + + + + +
                +

                Index

                +

                + + + Private + Readonly + RETRY_DELAY + + +
                + Type : number + +
                + Default value : 1000 +
                +
                - +
                - +
                + + + + + + + + + + + + + + +
                +
                Methods
                +
                + +
                + + +
                +

                Constructor

                + + + + + + + + + + + + + +
                +constructor(graphClient: GraphQLClient) +
                + +
                +
                + Parameters : + + + + + + + + + + + + + + + + + + +
                NameTypeOptional
                graphClient + GraphQLClient + + No +
                +
                +
                +
                + +
                + +

                + Methods +

                + + + + + + + + + + + + + + + + + + + +
                + + + Async + getRatio + + +
                + + getRatio() +
                + +
                + +
                + Returns : unknown + +
                +
                +
                + +

                + + +
                +
                import { gql, GraphQLClient } from 'graphql-request'
                +import { Injectable } from '@nestjs/common'
                +
                +@Injectable()
                +export class VoltBarClient {
                +  constructor (private graphClient: GraphQLClient) {}
                +
                +  async getRatio () {
                +    const query = gql`
                +      query ratio {
                +        bars {
                +          ratio
                +        }
                +      }
                +    `
                +
                +    const data = await this.graphClient.request<{ bars: { ratio: string }[] }>(query)
                +    return data?.bars?.[0]?.ratio
                +  }
                +}
                +
                +
                + +

                + + + + + + + + + + + + +

                +
                +

                results matching ""

                +
                  +
                  +
                  +

                  No results matching ""

                  +
                  +
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/injectables/VoltageDexService.html b/documentation/injectables/VoltageDexService.html index f584bb93..355ff3e5 100644 --- a/documentation/injectables/VoltageDexService.html +++ b/documentation/injectables/VoltageDexService.html @@ -93,6 +93,10 @@
                  Methods