Skip to content

Commit

Permalink
feat: nvm backend integration to record asset txs
Browse files Browse the repository at this point in the history
  • Loading branch information
aaitor committed Dec 19, 2023
1 parent d6e860d commit c89054e
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ NEVERMINED_PROXY_URI=https://proxy.nevermined.one

# for testing porposes
SEED_WORDS="taxi music thumb unique chat sand crew more leg another off lamp"


NVM_BACKEND_URL="http://localhost:3000"
NVM_BACKEND_AUTH="eyJhbG.dsadsads.dsdsadsa"
TRACK_BACKEND_TXS="true"
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ The Nevermined Node reads the following environment variables allowing the confi
| **NETWORK_AVERAGE_BLOCK_TIME** | The average block time in milliseconds for the connected network. Used to calculate the expiry time of the subscriptions token. Defaults 2100 milliseconds | `2100` |
| **NEVERMINED_SDK_LOG_LEVEL** | The log level of the nevermined sdk (0: Error, 1: Warn, 2: Log, 3: Verbose). Defaults to `0` | `0` |
| **ZERODEV_PROJECT_ID** | Optional [zerodev](https://zerodev.app/) _project id_ to allow the node to use a smart contract wallet | `4b1b45db-d7hg-864c-357a-8e9581b86358` |
| **NVM_BACKEND_URL** | Optional url of the Nevermined Backend. If given the backend can be used to store asset transactions | |
| **NVM_BACKEND_AUTH** | JWT token to authenticate in the Nevermined Backend | |
| **TRACK_BACKEND_TXS** | If `true` and the Nevermined Backend is configured via the `NVM_BACKEND_URL` env variable it will track the mint asset transactions | |

## Install and run:

Expand Down
4 changes: 4 additions & 0 deletions config/from-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const keys = [

'ZERODEV_PROJECT_ID',

'NVM_BACKEND_URL',
'NVM_BACKEND_AUTH',
'TRACK_BACKEND_TXS',

// for testing
'SEED_WORDS',
]
Expand Down
1 change: 1 addition & 0 deletions config/nevermined.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const LogLevel = require('@nevermined-io/sdk').LogLevel
const ethers = require('ethers').ethers
const { NeverminedOptions } = require('@nevermined-io/sdk')
const fs = require('fs')

const configBase = {
Expand Down
2 changes: 2 additions & 0 deletions integration/access.integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { JwtModule } from '@nestjs/jwt'
import { AuthService } from '../src/auth/auth.service.mock'
import { JwtStrategy } from '../src/common/strategies/jwt.strategy'
import { ConfigModule } from '../src/shared/config/config.module'
import { BackendModule } from 'src/shared/backend/backend.module'

/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */

Expand All @@ -20,6 +21,7 @@ describe('Info', () => {
const moduleRef = await Test.createTestingModule({
imports: [
NeverminedModule,
BackendModule,
ConfigModule,
PassportModule,
JwtModule.register({
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-ts",
"version": "2.1.0",
"version": "2.2.0",
"description": "Nevermined Node",
"main": "main.ts",
"scripts": {
Expand Down Expand Up @@ -36,7 +36,7 @@
"@nestjs/typeorm": "^10.0.0",
"@nevermined-io/argo-workflows-api": "^0.1.3",
"@nevermined-io/passport-nevermined": "^0.3.0",
"@nevermined-io/sdk": "^2.0.7",
"@nevermined-io/sdk": "^2.0.8",
"@nevermined-io/sdk-dtp": "^0.7.0-rc5",
"@sideway/address": "^5.0.0",
"@sideway/formula": "^3.0.1",
Expand Down
28 changes: 27 additions & 1 deletion src/access/access.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Public } from '../common/decorators/auth.decorator'
import { FileInterceptor } from '@nestjs/platform-express'
import crypto from 'crypto'
import { AssetResult, NeverminedService } from '../shared/nevermined/nvm.service'
import { AssetTransaction, BackendService } from '../shared/backend/backend.service'
import { TransferDto } from './dto/transfer'
import { UploadDto } from './dto/upload'
import { UploadResult } from './dto/upload-result'
Expand All @@ -54,7 +55,7 @@ export enum UploadBackends {
@ApiTags('Access')
@Controller()
export class AccessController {
constructor(private nvmService: NeverminedService) {}
constructor(private nvmService: NeverminedService, private backendService: BackendService) {}

@Get('access/:agreement_id/:index')
@ApiOperation({
Expand Down Expand Up @@ -229,6 +230,31 @@ export class AccessController {
)
}

try {
if (!this.backendService.isBackendEnabled()) {
Logger.log(`NVM Backend not enabled, skipping tracking transaction in the database`)
} else {
const assetPrice = this.nvmService.getAssetPrice(service) / 10n ** BigInt(4)
const assetTx: AssetTransaction = {
assetDid: did.getDid(),
assetOwner: subscriptionDDO.proof.creator,
assetConsumer: transferData.nftReceiver,
txType: 'Mint',
price: (Number(assetPrice) / 100).toString(),
currency: 'USDC',
paymentType: 'Crypto',
txHash: '0x0',
metadata: '',
}

const assetTxResult = await this.backendService.recordAssetTransaction(assetTx)
Logger.log(`Recording asset transaction with result: ${assetTxResult}`)
Logger.debug(`Asset transaction: ${JSON.stringify(assetTx)}`)
}
} catch (e) {
Logger.warn(`[${did.getDid()}] Failed to track transfer NFT ${e.message}`)
}

return 'success'
}

Expand Down
3 changes: 2 additions & 1 deletion src/access/access.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common'
import { NeverminedModule } from '../shared/nevermined/nvm.module'
import { BackendModule } from '../shared/backend/backend.module'
import { AccessController } from './access.controller'

@Module({
providers: [],
imports: [NeverminedModule],
imports: [NeverminedModule, BackendModule],
controllers: [AccessController],
exports: [],
})
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AuthModule } from './auth/auth.module'
import { EncryptModule } from './encrypt/encrypt.module'
import { AccessModule } from './access/access.module'
import { NeverminedModule } from './shared/nevermined/nvm.module'
import { BackendModule } from './shared/backend/backend.module'
import { ComputeModule } from './compute/compute.module'
import { SubscriptionsModule } from './subscriptions/subscriptions.module'

Expand All @@ -22,6 +23,7 @@ import { SubscriptionsModule } from './subscriptions/subscriptions.module'
NeverminedModule,
ComputeModule,
SubscriptionsModule,
BackendModule,
],
})
export class ApplicationModule {
Expand Down
11 changes: 11 additions & 0 deletions src/shared/backend/backend.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common'
import { HttpModule } from '@nestjs/axios'
import { BackendService } from './backend.service'
import { ConfigModule } from '../config/config.module'

@Module({
imports: [ConfigModule, HttpModule],
providers: [BackendService],
exports: [BackendService],
})
export class BackendModule {}
77 changes: 77 additions & 0 deletions src/shared/backend/backend.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Injectable, Logger } from '@nestjs/common'
import { InternalServerErrorException } from '@nestjs/common'
import { ConfigService } from '../config/config.service'
import { firstValueFrom } from 'rxjs'
import { HttpModuleOptions, HttpService } from '@nestjs/axios'

export interface AssetTransaction {
assetDid: string
assetOwner: string
assetConsumer: string
txType: string
price: string
currency: string
paymentType: string
txHash?: string
metadata?: string
}

@Injectable()
export class BackendService {
isNVMBackendEnabled = false
trackBackendTxs: boolean
backendUrl: string
backendAuth: string

constructor(private config: ConfigService, private readonly httpService: HttpService) {}

async onModuleInit() {
Logger.debug(`Starting BackendService`)

try {
this.trackBackendTxs = this.config.backendConfig().trackBackendTxs
this.backendUrl = this.config.backendConfig().backendUrl
this.backendAuth = this.config.backendConfig().backendAuth
this.isNVMBackendEnabled = this.config.backendConfig().isNVMBackendEnabled

Logger.log(`Backend Config: `)
Logger.log(`Is Backend Enabled: ${this.isNVMBackendEnabled}`)
Logger.log(this.trackBackendTxs)
Logger.log(this.backendUrl)
Logger.log(this.backendAuth)
} catch (e) {
Logger.warn(e)
}
}

public isBackendEnabled(): boolean {
return this.isNVMBackendEnabled
}

public async recordAssetTransaction(assetTx: AssetTransaction): Promise<boolean> {
if (!this.isNVMBackendEnabled || !this.trackBackendTxs) {
Logger.warn('Backend transactions recording is disabled by config')
return false
}

const requestConfig: HttpModuleOptions = {
url: `${this.backendUrl}/api/v1/transactions/asset`,
method: 'POST',
headers: {
Authorization: `Bearer ${this.backendAuth}`,
'Content-Type': 'application/json',
},
data: assetTx,
}

const response = await firstValueFrom(this.httpService.request(requestConfig))
const obj = response.data

if (obj.error) {
Logger.error('Backend returned an error message:', obj.error)
throw new InternalServerErrorException(obj.error)
}

return true
}
}
24 changes: 24 additions & 0 deletions src/shared/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export interface SubscriptionsConfig {
averageBlockTime: number
}

export interface BackendConfig {
isNVMBackendEnabled: boolean
trackBackendTxs: boolean
backendUrl: string
backendAuth: string
}

const configProfile = require('../../../config')

const DOTENV_SCHEMA = Joi.object({
Expand Down Expand Up @@ -88,6 +95,9 @@ const DOTENV_SCHEMA = Joi.object({
COMPUTE_PROVIDER_PASSWORD: Joi.string(),
NEVERMINED_PROXY_URI: Joi.string(),
ZERODEV_PROJECT_ID: Joi.string(),
NVM_BACKEND_URL: Joi.string(),
NVM_BACKEND_AUTH: Joi.string(),
TRACK_BACKEND_TXS: Joi.string(),
})

type DotenvSchemaKeys =
Expand Down Expand Up @@ -127,12 +137,16 @@ type DotenvSchemaKeys =
| 'SUBSCRIPTION_DEFAULT_EXPIRY_TIME'
| 'NETWORK_AVERAGE_BLOCK_TIME'
| 'ZERODEV_PROJECT_ID'
| 'NVM_BACKEND_URL'
| 'NVM_BACKEND_AUTH'
| 'TRACK_BACKEND_TXS'

export class ConfigService {
private readonly envConfig: EnvConfig
private readonly crypto: CryptoConfig
private readonly compute: ComputeConfig
private readonly subscriptions: SubscriptionsConfig
private readonly backend: BackendConfig

constructor() {
this.envConfig = this.validateInput(configProfile)
Expand Down Expand Up @@ -165,6 +179,12 @@ export class ConfigService {
defaultExpiryTime: this.get<string>('SUBSCRIPTION_DEFAULT_EXPIRY_TIME'),
averageBlockTime: this.get<number>('NETWORK_AVERAGE_BLOCK_TIME'),
}
this.backend = {
isNVMBackendEnabled: this.get<string>('NVM_BACKEND_URL') !== '',
trackBackendTxs: this.get<string>('TRACK_BACKEND_TXS') === 'true',
backendUrl: this.get<string>('NVM_BACKEND_URL'),
backendAuth: this.get<string>('NVM_BACKEND_AUTH'),
}
}

get<T>(path: DotenvSchemaKeys): T | undefined {
Expand All @@ -187,6 +207,10 @@ export class ConfigService {
return this.subscriptions
}

backendConfig(): BackendConfig {
return this.backend
}

getProviderBabyjub() {
return {
x: this.envConfig.PROVIDER_BABYJUB_PUBLIC1 || '',
Expand Down
9 changes: 9 additions & 0 deletions src/shared/nevermined/nvm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ServiceType,
generateInstantiableConfigFromConfig,
convertEthersV6SignerToAccountSigner,
ServiceCommon,
DDOError,
} from '@nevermined-io/sdk'
import {
BadRequestException,
Expand Down Expand Up @@ -489,6 +491,13 @@ export class NeverminedService {
return Number(duration) || 0
}

public getAssetPrice(service: ServiceCommon): bigint {
const assetPrice = DDO.getAssetPriceFromService(service)

if (assetPrice) return assetPrice.getTotalPrice()
throw new DDOError(`No price found for asset ${service.index}`)
}

/**
* Get the block number when a user bought the subscription
*
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1574,10 +1574,10 @@
snarkjs "^0.4.26"
web3-utils "^1.7.4"

"@nevermined-io/sdk@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@nevermined-io/sdk/-/sdk-2.0.7.tgz#b08129f22f0f0982d7b7f3930d7c423af2cfefbf"
integrity sha512-1k5eSK2kWDDb+6jjcK9Z1ayQCab7HRdShcYzAWXIj3O2qerM/kBPSbtHvFTYufsUThP+elGC/LfuTvSPyzClEA==
"@nevermined-io/sdk@^2.0.8":
version "2.0.8"
resolved "https://registry.yarnpkg.com/@nevermined-io/sdk/-/sdk-2.0.8.tgz#d28df59b690b2f48a3cfc1db3090ede5bcd7356a"
integrity sha512-T+HVgmpud9fjvdWVlEZIRXDrLlbjpjLm49Mi25uNqy6nMHN5F9D8H91+7xVW6EcVeWZKj/nT22ziFdFHBPMQ9w==
dependencies:
"@alchemy/aa-core" "0.1.1"
"@apollo/client" "^3.7.16"
Expand Down

0 comments on commit c89054e

Please sign in to comment.