Skip to content

Commit

Permalink
Merge pull request #1 from thenamespace/add-mongo-db-support
Browse files Browse the repository at this point in the history
Add mongo db support
  • Loading branch information
nenadmitt authored Feb 8, 2024
2 parents 0ef2832 + 73b3869 commit ad4c669
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 95 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
"@nestjs/mongoose": "^10.0.2",
"@nestjs/platform-express": "^10.0.0",
"ethers": "^6.10.0",
"mongoose": "^8.1.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"viem": "^2.7.3"
Expand Down
16 changes: 13 additions & 3 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { Module } from '@nestjs/common';
import { GatewayModule } from './gateway/gateway.module';
import { ConfigModule } from '@nestjs/config';
import { ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { AppProperties } from './configuration/app-properties';
import { AppPropertiesModule } from './configuration/app-properties.module';

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env'],
}),
GatewayModule],
controllers: [],
providers: [],
MongooseModule.forRootAsync({
imports: [AppPropertiesModule],
useFactory: async (configService: AppProperties) => ({
uri: configService.mongoConnectionString
}),
inject: [AppProperties]
}),
GatewayModule
],
})
export class AppModule {}
8 changes: 8 additions & 0 deletions src/configuration/app-properties.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { AppProperties } from './app-properties';

@Module({
exports: [AppProperties],
providers: [AppProperties],
})
export class AppPropertiesModule {}
14 changes: 14 additions & 0 deletions src/configuration/app-properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";

@Injectable()
export class AppProperties {

mongoConnectionString: string
signerWallet: string

constructor(private readonly configService: ConfigService) {
this.mongoConnectionString = this.configService.getOrThrow("MONGO_CONNECTION_STRING");
this.signerWallet = this.configService.getOrThrow("SIGNER_WALLET")
}
}
40 changes: 40 additions & 0 deletions src/gateway/db/resolved-ens.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';

@Schema({ timestamps: true, collection: "subnames", autoCreate: false, })
class Subname {
@Prop({ unique: true, required: true, type: String })
fullName: string;

@Prop({ required: true, type: String })
label: string;

@Prop({ required: true, type: String })
domain: string;

@Prop({ required: false, type: String })
ttl: number;

@Prop({
required: true,
type: Map,
default: [],
_id: false,
})
addresses: Map<string, string>;

@Prop({
required: false,
type: Map,
default: [],
_id: false,
})
textRecords: Map<string, string>;

@Prop({ required: false, type: String })
contentHash: string;
}

export const SUBANME_DOMAIN = 'Subname';
export const SubnameSchema = SchemaFactory.createForClass(Subname);
export type SubnameDocument = mongoose.HydratedDocument<Subname>;
14 changes: 14 additions & 0 deletions src/gateway/db/resovled-ens.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { InjectModel } from "@nestjs/mongoose";
import { SUBANME_DOMAIN, SubnameDocument } from "./resolved-ens.schema";
import { Model } from "mongoose";
import { Injectable } from "@nestjs/common";

@Injectable()
export class ResolvedEnsRepository {
@InjectModel(SUBANME_DOMAIN)
dao: Model<SubnameDocument>

public async findOne(query: Record<string,any>): Promise<SubnameDocument | null> {
return this.dao.findOne(query);
}
}
40 changes: 0 additions & 40 deletions src/gateway/gateway.handler.ts

This file was deleted.

30 changes: 24 additions & 6 deletions src/gateway/gateway.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import { Module } from "@nestjs/common";
import { GatewayController } from "./gateway.controller";
import { GatewayService } from "./gateway.service";
import { Module } from '@nestjs/common';
import { GatewayController } from './gateway.controller';
import { GatewayService } from './gateway.service';
import { MongooseModule } from '@nestjs/mongoose';
import { SUBANME_DOMAIN, SubnameSchema } from './db/resolved-ens.schema';
import { GatewayDatabaseResolver } from './resolver/database.resolver';
import { AppPropertiesModule } from 'src/configuration/app-properties.module';
import { ResolvedEnsRepository } from './db/resovled-ens.repository';

@Module({
controllers: [GatewayController],
providers: [GatewayService]
imports: [
MongooseModule.forFeature([
{
schema: SubnameSchema,
name: SUBANME_DOMAIN,
},
]),
AppPropertiesModule,
],
controllers: [GatewayController],
providers: [
GatewayService,
GatewayDatabaseResolver,
ResolvedEnsRepository
],
})
export class GatewayModule {}
export class GatewayModule {}
76 changes: 58 additions & 18 deletions src/gateway/gateway.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import {
Address,
Hash,
Expand All @@ -9,21 +9,34 @@ import {
encodeFunctionResult,
encodePacked,
keccak256,
namehash,
parseAbiParameters,
} from 'viem';
import { decodeDnsName } from './gateway.utils';
import RESOLVER_ABI from './resolver.json';
import RESOLVER_ABI from './resolver_abi.json';
import { privateKeyToAccount } from 'viem/accounts';
import { ConfigService } from '@nestjs/config';
import { ethers } from 'ethers';
import { GatewayResolver } from './resolver/gatway.resolver';
import { AppProperties } from 'src/configuration/app-properties';
import { GatewayDatabaseResolver } from './resolver/database.resolver';

const addr = 'addr';
const text = 'text';
const contentHash = 'contentHash';
const supportedFunctions = [addr, text, contentHash];
const defaultCoinType = '60';

@Injectable()
export class GatewayService {
viemSigner: PrivateKeyAccount;
ethersSigner: ethers.SigningKey;

constructor(private readonly config: ConfigService) {
const privateKey = this.config.getOrThrow('SIGNER_WALLET') as string;
private viemSigner: PrivateKeyAccount;
private ethersSigner: ethers.SigningKey;

constructor(
private readonly appProperties: AppProperties,
// doesn't work with GatewayResolver interface, investigate why
private readonly resolver: GatewayDatabaseResolver,
) {
const privateKey = this.appProperties.signerWallet;
const _pk = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`;
this.viemSigner = privateKeyToAccount(_pk as Hash);

Expand All @@ -40,7 +53,6 @@ export class GatewayService {
// 8 - 'resolve' function signature
const data = callData.substring(10);

console.log(`Signer address ${this.viemSigner.address}`);
const parsedCallData = decodeAbiParameters(
parseAbiParameters('bytes name, bytes callData'),
`0x${data}`,
Expand All @@ -62,15 +74,16 @@ export class GatewayService {
`Request for resolving name ${decodedName} with function ${decodedFunction.functionName}, with params ${decodedFunction.args}`,
);

const { resultAddress, ttl } = this.resolveResult(
const { value, ttl } = await this.resolveResult(
decodedName,
decodedFunction.functionName,
decodedFunction.args,
);

const result = encodeFunctionResult({
abi: RESOLVER_ABI,
functionName: decodedFunction.functionName,
result: [resultAddress],
result: [value],
});

const digest = keccak256(
Expand All @@ -94,8 +107,12 @@ export class GatewayService {
// message: digest,
// });

const sig = this.ethersSigner.sign(digest)
const ethersSig = ethers.concat([sig.r, sig.s, new Uint8Array([sig.v])]) as any;
const sig = this.ethersSigner.sign(digest);
const ethersSig = ethers.concat([
sig.r,
sig.s,
new Uint8Array([sig.v]),
]) as any;

const finalResult = encodeAbiParameters(
parseAbiParameters('bytes response, uint64 ttl, bytes signature'),
Expand All @@ -107,10 +124,33 @@ export class GatewayService {
};
}

private resolveResult = (ensName: string, functionName: string) => {
return {
resultAddress: '0x3E1e131E7e613D260F809E6BBE6Cbf7765EDC77f',
ttl: Date.now(),
};
private resolveResult = async (
ensName: string,
functionName: string,
args: readonly any[],
) => {
const nameNode = namehash(ensName);
const funcNode = args[0];

if (nameNode !== funcNode) {
throw new BadRequestException('Namehash missmatch');
}

if (supportedFunctions.includes(functionName)) {
throw new BadRequestException('Unsupported opperation ' + functionName);
}

switch (functionName) {
case addr:
const coinType = args.length > 1 ? args.length[1] : defaultCoinType;
return this.resolver.getAddress(ensName, coinType);
case text:
if (args.length < 2) {
throw new BadRequestException('Text key not found');
}
return this.resolver.getText(ensName, args[1]);
default:
return this.resolver.getContentHash(ensName);
}
};
}
62 changes: 62 additions & 0 deletions src/gateway/resolver/database.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GatewayResolver, ResolverResult } from './gatway.resolver';
import { ResolvedEnsRepository } from '../db/resovled-ens.repository';

// 60 for ethereum address
const DEFAULT_ADDRESS_COIN_TYPE = '60';

@Injectable()
export class GatewayDatabaseResolver implements GatewayResolver {
constructor(private readonly repository: ResolvedEnsRepository) {}

public async getText(fullName: string, key: string): Promise<ResolverResult> {;
const subname = await this.getSubname(fullName);

if (subname.textRecords && subname.textRecords[key]) {
return {
value: subname.textRecords[key],
ttl: subname.ttl || 0
}
}
return {
ttl: 0,
value: ""
};
}

public async getAddress(fullName: string, coinType?: string): Promise<ResolverResult> {
const subname = await this.getSubname(fullName);
const _coin = coinType || DEFAULT_ADDRESS_COIN_TYPE;
if (subname.addresses && subname.addresses[_coin]) {
return {
value: subname.addresses[_coin],
ttl: subname.ttl || 0
}
}
return {
ttl: 0,
value: ""
};
}

public async getContentHash(fullName: string): Promise<ResolverResult> {
const subname = await this.getSubname(fullName);
return {
value: subname.contentHash || "",
ttl: subname.ttl || 0
}
}

private getSubname = async (ensName: string) => {
const subname = await this.repository.findOne({
fullName: ensName
});
if (!subname && !subname.id) {
throw new NotFoundException(`Subname not found`);
}
return subname;
};
}
Loading

0 comments on commit ad4c669

Please sign in to comment.