Skip to content

Commit

Permalink
Prototyping: Unlocking Data NFT Delegations (#2)
Browse files Browse the repository at this point in the history
* add admin native auth token generation

* add data unlocking to collections endpoint
  • Loading branch information
michavie authored Feb 26, 2024
1 parent 65735f9 commit c6f5f0f
Show file tree
Hide file tree
Showing 10 changed files with 735 additions and 543 deletions.
7 changes: 6 additions & 1 deletion apps/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const config = () => ({
app: {
name: process.env.APP_NAME || 'aggregator',
env: process.env.APP_ENV || 'mainnet',
url: process.env.APP_DOMAIN || 'https://itheum.io',
},
features: {
publicApi: {
Expand All @@ -22,8 +23,12 @@ export const config = () => ({
},
chain: {
apiUrl: process.env.CHAIN_API_URL || 'https://api.multiversx.com',
wallet: {
admin: process.env.WALLET_ADMIN_PEM || '####',
},
nativeAuth: {
acceptedOrigins: ['https://utils.multiversx.com'],
maxExpirySeconds: 3600,
acceptedOrigins: ['https://itheum.io', 'https://utils.multiversx.com'],
},
},
redis: {
Expand Down
13 changes: 9 additions & 4 deletions apps/api/src/endpoints/collections/collections.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ApiResponse } from '@nestjs/swagger'
import { AppService } from '@mvx-monorepo/common'
import { DataValue } from './entities/data.value'
import { AppService, DataService } from '@mvx-monorepo/common'
import { DelegationService } from '@mvx-monorepo/common/delegation'
import { Controller, Get, NotFoundException, Param } from '@nestjs/common'

@Controller()
export class CollectionsController {
constructor(
private readonly appService: AppService,
private readonly delegationService: DelegationService
private readonly delegationService: DelegationService,
private readonly dataService: DataService
) {}

// TODO: @UseGuards(NativeAuthGuard)
Expand All @@ -32,7 +33,9 @@ export class CollectionsController {
}

const delegations = await this.delegationService.getDelegations(app)
const values = delegations.map((del) => DataValue.fromDelegation(del))
const unlockedDelegations = await this.dataService.unlockDelegationData(delegations)

const values = unlockedDelegations.map((del) => DataValue.fromDelegation(del))

return values
}
Expand All @@ -52,7 +55,9 @@ export class CollectionsController {
? await this.delegationService.getDelegationsBySegment(app, key)
: await this.delegationService.getDelegations(app)

const values = delegations.map((del) => DataValue.fromDelegation(del))
const unlockedDelegations = await this.dataService.unlockDelegationData(delegations)

const values = unlockedDelegations.map((del) => DataValue.fromDelegation(del))

return values
}
Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/endpoints/collections/entities/data.value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Delegation } from '@mvx-monorepo/common/delegation'
export class DataValue {
collection: string
nonce: number
content?: any

constructor(collection: string, nonce: number) {
constructor(collection: string, nonce: number, content?: any) {
this.collection = collection
this.nonce = nonce
this.content = content
}

static fromDelegation(delegation: Delegation): DataValue {
return new DataValue(delegation.collection, delegation.nonce)
return new DataValue(delegation.collection, delegation.nonce, delegation.content)
}
}
12 changes: 10 additions & 2 deletions apps/api/src/endpoints/endpoints.controllers.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ import { AuthController } from './auth/auth.controller'
import { AppsController } from './apps/apps.controller'
import { EndpointsServicesModule } from './endpoints.services.module'
import { CollectionsController } from './collections/collections.controller'
import { AppService, ContractService, DelegationService, DynamicModuleUtils, HealthCheckController } from '@mvx-monorepo/common'
import {
AppService,
DataService,
AdminService,
ContractService,
DelegationService,
DynamicModuleUtils,
HealthCheckController,
} from '@mvx-monorepo/common'

@Module({
imports: [EndpointsServicesModule, DynamicModuleUtils.getCachingModule(config)],
providers: [DynamicModuleUtils.getNestJsApiConfigService(), AppService, DelegationService, ContractService],
providers: [DynamicModuleUtils.getNestJsApiConfigService(), AppService, AdminService, DelegationService, DataService, ContractService],
controllers: [AuthController, HealthCheckController, AppsController, CollectionsController],
})
export class EndpointsControllersModule {}
6 changes: 6 additions & 0 deletions apps/cache-warmer/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export const config = () => ({
aggregator: process.env.CONTRACT_AGGREGATOR || '####',
},
services: {
chain: {
apiUrl: process.env.CHAIN_API_URL || 'https://api.multiversx.com',
nativeAuth: {
acceptedOrigins: ['https://utils.multiversx.com'],
},
},
redis: {
host: process.env.REDIS_HOST || '127.0.0.1',
port: parseInt(process.env.REDIS_PORT || '6379'),
Expand Down
22 changes: 20 additions & 2 deletions libs/common/src/admin/admin.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { config } from 'apps/api/config'
import { Injectable } from '@nestjs/common'
import { AppConfigService } from '../config'
import { UserSigner } from '@multiversx/sdk-wallet'
import { Account, Transaction } from '@multiversx/sdk-core'
import { Account, SignableMessage, Transaction } from '@multiversx/sdk-core'
import { NativeAuthClient } from '@multiversx/sdk-native-auth-client'

@Injectable()
export class AdminService {
public readonly signer: UserSigner
public readonly account: Account

constructor(private readonly appConfigService: AppConfigService) {
this.signer = UserSigner.fromPem('####')
this.signer = UserSigner.fromPem(config().services.chain.wallet.admin)
this.account = new Account(this.signer.getAddress())
}

Expand Down Expand Up @@ -37,4 +39,20 @@ export class AdminService {

return tx
}

async generateNativeAuthToken() {
const client = new NativeAuthClient({
apiUrl: config().services.chain.apiUrl,
expirySeconds: config().services.chain.nativeAuth.maxExpirySeconds,
origin: config().app.url,
})

const init = await client.initialize()
const address = this.account.address.bech32()
const signable = new SignableMessage({ message: Buffer.from(`${address}${init}`) })
const signature = await this.signer.sign(signable.serializeForSigning())
const signatureHex = signature.toString('hex')

return client.getToken(address, init, signatureHex)
}
}
58 changes: 40 additions & 18 deletions libs/common/src/data/data.service.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
import { AdminService } from '../admin'
import { config } from 'apps/api/config'
import { Delegation } from '../delegation'
import { Injectable } from '@nestjs/common'
import { DataNft } from '@itheum/sdk-mx-data-nft/out'
import { CacheService } from '@multiversx/sdk-nestjs-cache'

@Injectable()
export class DataService {
constructor(protected readonly cachingService: CacheService) {}

async mintStreamNft() {
// const dataNfts = DataNft.createManyFromApi()
// const tx = await dataNftMinter.mint(
// this.adminService.getAccount(network).address,
// 'Portable Data #1',
// 'https://api.itheumcloud-stg.com/datamarshalapi/achilles/v1',
// 'https://peerme.io/explore',
// 'https://peerme.io/',
// 5,
// 1,
// 'Valuable Data',
// 'This is some valuable stuff here',
// 500 * 10 ** 18
// )
// await this.adminService.signAndSend(tx as any, network)
// console.log(`Minted DataNFT on ${network} with tx: ${tx.getHash().toString()}`)
constructor(
protected readonly cachingService: CacheService,
protected readonly adminServie: AdminService
) {
DataNft.setNetworkConfig(config().app.env)
}

async unlockDelegationData(delegations: Delegation[]): Promise<Delegation[]> {
const nativeAuthToken = await this.adminServie.generateNativeAuthToken()
const unlockables = await DataNft.createManyFromApi(delegations.map((d) => ({ tokenIdentifier: d.collection, nonce: d.nonce })))

const viewDataRequestOptions = this.getDataViewRequestOptions(nativeAuthToken)
const unlocks = unlockables.map((u) => u.viewDataViaMVXNativeAuth(viewDataRequestOptions))
const unlockResults = await Promise.all(unlocks)

// TODO: add filtering based on status checks, and potentially trigger automatic undelegations

const contentResults = await Promise.all(unlockResults.map((x) => x.data.text()))

const unlockedDelegations = delegations.map((del, i) => {
del.content = contentResults[i]
return del
})

return unlockedDelegations
}

private getDataViewRequestOptions(nativeAuthToken: string) {
return {
mvxNativeAuthOrigins: config().services.chain.nativeAuth.acceptedOrigins,
mvxNativeAuthMaxExpirySeconds: config().services.chain.nativeAuth.maxExpirySeconds,
fwdHeaderMapLookup: {
authorization: `Bearer ${nativeAuthToken}`,
},
asDeputyOnAppointerAddr: config().contracts.aggregator,
}
}
}
1 change: 1 addition & 0 deletions libs/common/src/delegation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type Delegation = {
collection: string
nonce: number
segment: string
content?: any
}
Loading

0 comments on commit c6f5f0f

Please sign in to comment.