-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [sc-25579] Create TypeScript SDK for NameRank API (#472)
* init namerank-sdk * update lock
- Loading branch information
Showing
7 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
dist/ | ||
node_modules/ | ||
npm-debug.log | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "@namehash/namerank", | ||
"private": true, | ||
"description": "A lightweight JavaScript client for the NameRank API.", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/namehash/namekit.git", | ||
"directory": "packages/namerank-sdk" | ||
}, | ||
"homepage": "https://github.com/namehash/namekit/tree/main/packages/namerank-sdk", | ||
"keywords": [ | ||
"ENS", | ||
"NameKit", | ||
"NameHash", | ||
"NameGuard", | ||
"NameRank", | ||
"NFT" | ||
], | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"types": "./dist/index.d.ts", | ||
"import": "./dist/index.js", | ||
"default": "./dist/index.js" | ||
} | ||
}, | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"README.md" | ||
], | ||
"sideEffects": false, | ||
"scripts": { | ||
"build": "tsup", | ||
"dev": "tsup --watch --clean=false", | ||
"test": "vitest" | ||
}, | ||
"dependencies": { | ||
"@namehash/ens-utils": "workspace:*", | ||
"@namehash/nameguard": "workspace:*", | ||
"cross-fetch": "4.0.0" | ||
}, | ||
"devDependencies": { | ||
"@namekit/tsconfig": "workspace:*", | ||
"@types/node": "22.7.4", | ||
"tsup": "8.3.5", | ||
"typescript": "5.6.2", | ||
"vitest": "1.6.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { describe, it, expect } from "vitest"; | ||
|
||
import { createClient, MAX_INSPECTED_NAME_CHARACTERS } from "."; | ||
|
||
const namerank = createClient({ | ||
// undefined will default to the production endpoint | ||
namerankEndpoint: process.env.NAMERANK_API_URI, | ||
}); | ||
|
||
// No mocking (we should probably mock) | ||
// Test urlencoded | ||
|
||
describe("inspectName", () => { | ||
it("should fetch the NameRank report for a single name", async () => { | ||
const data = await namerank.inspectName("vitalìk.eth"); | ||
|
||
expect(data.namerank.purity_score).toBeCloseTo(0.28995870588235295, 2); | ||
expect(data.namerank.interesting_score).toBeCloseTo(0.3520927142857143, 2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import fetch from "cross-fetch"; | ||
import { Network, NameGuardReport } from "@namehash/nameguard"; | ||
|
||
const ETH_TLD = "eth"; | ||
|
||
enum LabelStatus { | ||
Normalized = 'normalized', | ||
Unnormalized = 'unnormalized', | ||
Unknown = 'unknown' | ||
} | ||
|
||
interface NLPLabelAnalysis { | ||
inspection: any; | ||
status: LabelStatus; | ||
probability: number; | ||
log_probability: number; | ||
word_count: number; | ||
top_tokenization?: string[]; | ||
tokenizations: Record<string, any>[]; | ||
} | ||
|
||
interface NameRankReport { | ||
purity_score: number; | ||
interesting_score: number; | ||
analysis?: NLPLabelAnalysis; | ||
} | ||
|
||
interface NameRankResponse { | ||
namerank: NameRankReport; | ||
nameguard: NameGuardReport; | ||
} | ||
|
||
// TODO: Let's apply more formalization to this error class. | ||
class NameRankError extends Error { | ||
constructor( | ||
public status: number, | ||
message?: string, | ||
) { | ||
super(message); | ||
} | ||
} | ||
|
||
const DEFAULT_ENDPOINT = "https://api.nameguard.io/namerank"; | ||
const DEFAULT_NETWORK: Network = "mainnet"; | ||
const DEFAULT_INSPECT_LABELHASH_PARENT = ETH_TLD; | ||
export const DEFAULT_COMPUTE_NAMEGUARD_REPORT = false; | ||
const MAX_BULK_INSPECTION_NAMES = 250; | ||
|
||
/** includes label separators */ | ||
export const MAX_INSPECTED_NAME_CHARACTERS = 200; | ||
|
||
/** includes duplicated unknown labels */ | ||
const MAX_INSPECTED_NAME_UNKNOWN_LABELS = 5; | ||
|
||
export interface NameRankOptions { | ||
namerankEndpoint?: string; | ||
network?: Network; | ||
} | ||
|
||
interface InspectNameOptions {} | ||
|
||
export class NameRank { | ||
private namerankEndpoint: URL; | ||
protected network: Network; | ||
private abortController: AbortController; | ||
|
||
constructor({ | ||
namerankEndpoint = DEFAULT_ENDPOINT, | ||
network = DEFAULT_NETWORK, | ||
}: NameRankOptions = {}) { | ||
this.namerankEndpoint = new URL(namerankEndpoint); | ||
console.log(this.namerankEndpoint); | ||
this.network = network; | ||
this.abortController = new AbortController(); | ||
} | ||
|
||
/** | ||
* Inspects a single name with NameRank. | ||
* | ||
* If `name` includes unknown labels then this function will attempt automated labelhash resolution through the ENS Subgraph | ||
* using the network specified in the `NameGuard` instance. Therefore the returned `name` may not match the provided `name`, but is guaranteed to have a matching `namehash`. | ||
* | ||
* @param {string} name The name for NameRank to inspect. | ||
* @param {InspectNameOptions} options The options for the inspection. | ||
* @returns {Promise<NameRankReport>} A promise that resolves with the `NameRankReport` of the name. | ||
* @example | ||
* const nameRankReport = await namerank.inspectName('vitalik.eth'); | ||
*/ | ||
public inspectName( | ||
name: string, | ||
options?: InspectNameOptions, | ||
): Promise<NameRankResponse> { | ||
const network_name = this.network; | ||
return this.rawRequest("inspect-name", "POST", { name /*, network_name */ }); | ||
} | ||
|
||
/** | ||
* Performs a raw HTTP request to the NameRank API. | ||
* @param {string} path The API endpoint path. | ||
* @param {string} method The HTTP method (e.g., 'GET', 'POST'). | ||
* @param {object} body The request body for POST requests. | ||
* @param {object} headers Additional headers for the request. | ||
* @returns {Promise<any>} The response from the API. | ||
*/ | ||
async rawRequest( | ||
path: string, | ||
method: string = "GET", | ||
body: object = {}, | ||
headers: object = {}, | ||
): Promise<any> { | ||
const url = new URL(this.namerankEndpoint); | ||
url.pathname += "/" + path; | ||
|
||
const options: RequestInit = { | ||
method, | ||
headers: { | ||
"Content-Type": "application/json", | ||
...headers, | ||
}, | ||
signal: this.abortController.signal, | ||
}; | ||
|
||
if (method !== "GET") { | ||
options.body = JSON.stringify(body); | ||
} | ||
|
||
console.log(url); | ||
const response = await fetch(url, options); | ||
|
||
if (!response.ok) { | ||
throw new NameRankError( | ||
response.status, | ||
`Failed to perform request to ${path}.`, | ||
); | ||
} | ||
|
||
return await response.json(); | ||
} | ||
|
||
public abortAllRequests(): void { | ||
this.abortController.abort(); | ||
|
||
this.abortController = new AbortController(); | ||
} | ||
} | ||
|
||
/** | ||
* Creates a new instance of a NameRank client. | ||
* | ||
* @param {NameRankOptions} [options] - Configuration options for NameRank. | ||
* @example | ||
* import { createClient } from '@namehash/namerank'; | ||
* const namerank = createClient({ ... }) | ||
*/ | ||
export function createClient(options?: NameRankOptions) { | ||
return new NameRank(options); | ||
} | ||
|
||
const defaultClient = createClient(); | ||
|
||
/** | ||
* `NameRank` provides methods to inspect and rank ENS names. | ||
* It can inspect individual names or batch names. | ||
* @example | ||
* import { namerank } from '@namehash/namerank'; | ||
* const nameRankReport = await namerank.inspectName('vitalik.eth'); | ||
*/ | ||
export const namerank = defaultClient; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"$schema": "https://json.schemastore.org/tsconfig", | ||
"extends": "@namekit/tsconfig/base.json", | ||
"compilerOptions": { | ||
"target": "ES2020", | ||
"module": "ES2020", | ||
"paths": { | ||
"@namehash/ens-utils": ["../ens-utils/dist"], | ||
"@namehash/nameguard": ["../nameguard-sdk"] | ||
} | ||
}, | ||
"include": ["src/**/*"], | ||
"exclude": ["node_modules", "dist"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { defineConfig } from "tsup"; | ||
|
||
export default defineConfig({ | ||
entry: ["src/index.ts"], | ||
format: ["esm"], | ||
clean: true, | ||
dts: true, | ||
skipNodeModulesBundle: true, | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.