Nom Payment Portal API Spec === ###### tags: `Technical Specification` # General Note - [Nom Payment Portal API Github Repository](https://github.com/collectiveactions/nom-payment-portal-api) - This document describes the specification for the Nom Payment Portal API that serves as the temporary API sets for storing namespace, name types, and names purchased by the end user of Nom - The purpose of this document is to make clear all the validations that are required in order to not break the data integrity of the said entities (Namespace, Nametype, Names) while they are not yet blessed by the Data Integrity of Holochain. # Specification ## Deployment - This set of APIs are deployed using Cloudflare Workers ## Storage - We are using Cloudflare's `Durable Object` store. - the `Durable Object` stores all namespaces and names in a path structure that matches the path structure we will have in Holochain (see below) - the Namespace ID is generated using the Durable Object API `newUniqueId` which guarantees uniqueness of data in the same namespace of Object. Read more [here](https://developers.cloudflare.com/workers/runtime-apis/durable-objects/#generating-ids-randomly) - The actual values of the `Namespace` details are stored in the Durable Object. This is to ensure that writes on the same Namespace have strong consistency. Read more [here](https://developers.cloudflare.com/workers/learning/how-kv-works/) ## Durable Object Store Path Structure - Some of the types here are yet to be defined - Types used here are defined [here](#Types-and-Validation) ```json // Nom Registry Durable Object // key glossary // {customerPK} - public key of customer generated by Chaperone // {arthur} - an example global agent name purchased by a customer // {holochain} - an example global org name purchased by a customer // {namespaceuuid} - temporary namespace uuid // {username} - the path of a name type // {meister} - an example namespac bound name purchased by a customer { // WITNESS "witness/{customerPK}": WitnessRegistration, // information of the witness registering // GLOBAL NAMES "nomnom/{arthur}": NomNomRegistration // the details of the global agent name purchased "orgnom/{holochain}": OrgNomRegistration // LOCAL NAMES "registration/{namespace_uuid}/{agentname}/{meister}": NameRegistration // name registered for a particular name type in a namespace // NAMESPACE "customer/namespace/{customerPK}": namespaceId[], // all namespaces a customer has bought. Used for query "namespace/{namespace_uuid}/details": Namespace, "namespace/{namespace_uuid}/properties": NamespaceProperties, "nametypes/{namespace_uuid}/{agentname}/details": NameType, } // This file structure is based on this file structure initially suggested ~/nom_registry ./witnesses ./witness_profile.json ./global_names ./gusername1.json ./gusername2.json ./namespaces ./app_namespace_index ./namespace_properties.json ./username ./username1.json ./username2.json ./username3.json ./other_name_type ./name1.json ``` ## APIs - base url: api.nomde.net ### Creates #### **Register Customer(User)** - URI: `.post(customers/{id})` - input ```typescript // from URL // customer {id} - the public key of the API caller in Base64 string // output export interface RegisterCustomerResponse { success: boolean, pubKey: string } // sample output { success: true, pubKey: "uchA83710knldngalweghowahanflfew2" } ``` #### **create namespace** - URI: `.post('customers/{id}/namespaces')` - input ```typescript // from url // customers {id} - the public key of the api caller generated by Chaperone // request body export type CreateNamespaceInput = { NomNomRegOptIn: boolean; // this is the global name scope for agents OrgNomRegOptIn: boolean; // this is the global name scope for organizations bindingPublicKey: string; properties: CreateNamespaceProperties; signature: string; // signature to this input that can be verified with the public key token: string; // token that is provided as a proof of payment by the payment gateway }; export type CreateNamespaceProperties = { displayName: string; dnaVersions: string[]; orgDetail: Organization; nameTypes: CreateNameTypeInput[]; nomNomNameType?: string; // path of the name type for agent in nameTypes array orgNomNameType?: string; // path of the name type for organization in nameTypes array maintainers: string[]; // Agent public keys of maintainers. Set by the owner of namespace. marketingInfo: MarketingInfo; ownershipProof: string; // Option<Signature>. Signature of the owner of namespace. }; type CreateNameTypeInput = { path: string; // immutable(permanent) name of the name type validNameStructureRegex: string; reservedNames: string[]; displayName: string; ttl: number; // u8 purchasePrice: number; // u32 (in USD cents) prePurchasedNameAllotments: CreatePrePurchasedNameAllotment[]; // can only be added. Delete is not allowed. signature: string; // signature to this input that can be verified with the public key binding: NameTypeBinding; }; type CreatePrePurchasedNameAllotmentInput = { nameTypePath: string; qty: number; // bundle of free names purchased }; enum NameTypeBinding { NomNoms = "NomNoms", OrgNoms = "OrgNoms", NoBinding = "NoBinding", } type Organization = { name: string; email: string; country: string; province: string; city: string; contactNo: string; }; type MarketingInfo = { logo?: string; // base64 of logo appName: string; description?: string; appStatus: AppStatus; }; enum AppStatus { Development = "Development", Alpha = "Alpha", Beta = "Beta", } // Sample Input: const sampleInputData = { nomNomRegOptIn: false, orgNomRegOptIn: false, bindingPublicKey: "samplePubKey", properties: { displayName: "sampleDisplayName", dnaVersions: ["sampleDnaVersion"], orgDetail: { name: "sampleOrgName", email: "sample@email.com", country: "sampleOrgCountry", contactNo: "sampleOrgContactNo", city: "sampleOrgCity", province: "sampleOrgProvince", }, nameTypes: [ { path: "sampleNameTypePath", validNameStructureRegex: "/[abc]dd+/g", reservedNames: ["sampleReservedName"], displayName: "sampleNameTypeDisplayName", ttl: 1, purchasePrice: 100, prePurchasedNameAllotments: [ { nameTypePath: "sampleNameTypePath", qty: 1000, }, ], binding: "NoBinding", }, ], maintainers: ["sampleMaintainer"], marketingInfo: { appName: "sampleDescription", appStatus: "Alpha", }, ownershipProof: "sampleOwnershipProof", }, signature: "sampleSignature", token: "sampleToken", }; // output interface CreateNamespaceResponse { success: boolean; data: { namespace: Namespace; properties: NamespaceProperties; nameTypes: { [key: string]: NameType; }; }; } // Sample Output: { success: boolean; data: { namespace: { nomNomRegOptIn: false; orgNomRegOptIn: false; ttl: 1; bindingPublicKey: "uchcAGeahgoowaugtnlekwnt!@3H#T2ipqtn"; }; properties: { namespace: "305q295892q3ytqiohwhn3tognlewanfe"; displayName: "displayName"; dnaVersions: ["uchgfaewukbglaewjbg", "ahgeeohgboawlbnwglkbga"]; orgDetail: { name: "sampleOrgName", email: "sample@email.com", country: "sampleOrgCountry", contactNo: "sampleOrgContactNo", city: "sampleOrgCity", province: "sampleOrgProvince", }; nameTypes: { "sampleNameTypePath": "sampleNameTypePath" "sampleNameTypePath2": "sampleNameTypePath2" }; nomNomNameType?: "sampleNameTypePath"; orgNomNameType?: "sampleNameTypePath2"; maintainers: ["ucugblewabglknek;aw", "aehwgubawelgblaewknglkaw"]; marketingInfo: { logo?: "geawgewajgnwajlnglawegewa"; appName: "sampleAppName"; appStatus: "Development"; description: "sample description" }; ownershipProof: "aebgfiaewgjbawejlbgjkwebgjlaew"; }; nameTypes: { "sampleNameTypePath": { path: "sampleNameTypePath", namespace: "305q295892q3ytqiohwhn3tognlewanfe"; validNameStructureRegex: "/[abc]dd+/g", reservedNames: ["admin", "org"]; displayName: "username"; ttl: 3; purchasePrice: 100; // in cents prePurchasedNameAllotments: [{ namespace: "305q295892q3ytqiohwhn3tognlewanfe"; nameTypePath: "sampleNameTypePath"; qty: 10000; }] }; }; }; } ``` - Required Validation: - `Namespace: Create` - `NamespaceProperties: Create` - `NameType: Create` #### **create(add) nametype** - URI: `.post('customers/{id}/namespaces/{id}/nametype')` - input ```typescript // From url // customers {id} - the public key of the api caller generated by Chaperone // namespace {id} - a random, unique id generated by Cloudflare's Transaction Storage API enum NameTypeBinding = { NomNoms, OrgNoms, NoBinding } export type CreateNameTypeInput = { namespace: string, // namespace ID path: string; // immutable(permanent) name of the name type validNameStructureRegex: string; reservedNames: string[]; displayName: string; ttl: number; // u8 purchasePrice: number; // u32 (in USD cents) prePurchasedNameAllotments: CreatePrePurchasedNameAllotmentInput[]; // can only be added. Delete is not allowed. signature: string; // signature to this input that can be verified with the public key binding: NameTypeBinding; }; export type CreatePrePurchasedNameAllotmentInput = { nameTypePath: string; qty: number; // bundle of free names purchased }; // sample input const createNameTypeInput: CreateNameTypeInput = { namespace: "namespaceId", signature: "sampleSignature", path: "sampleNameTypePath2", validNameStructureRegex: "/[abc]+/g", reservedNames: ["sampleReservedName"], displayName: "sampleNameTypeDisplayName", ttl: 1, purchasePrice: 0, prePurchasedNameAllotments: [ { nameTypePath: "sampleNameTypePath2", qty: 1000, }, ], binding: "NoBinding", }; // sample output CreateNameTypeResponse { success: true; data: { path: "sampleNameTypePath", namespace: "305q295892q3ytqiohwhn3tognlewanfe"; validNameStructureRegex: "/[abc]dd+/g", reservedNames: ["admin", "org"]; displayName: "username"; ttl: 3; purchasePrice: 100; // in cents prePurchasedNameAllotments: [{ namespace: "305q295892q3ytqiohwhn3tognlewanfe"; nameTypePath: "sampleNameTypePath"; qty: 10000; }] }; } ``` - Required Valdation: - `Nametype: Create` - `NamespaceProperties: Update`- For updating `Nametypes` #### **create(add) pre purchased names for nametype** - URI: `.put('customers/{id}/namespaces/nametype/{id}/pre-purchased-names')` - input ```typescript // from url // customer {id} - public key of the caller provided by Chaperone // nametype {id} - path of the name type type PrePurchasedNameAllotment = { namespace: string; nameTypePath: string; qty: number; // bundle of free names purchased }; type AddPrePurchasedNameAllotmentInput = PrePurchasedNameAllotment & { signature?: string; // signature to this input that can be verified with the public key token?: string; // token that is provided as a proof of payment by the payment gateway }; // sample input { namespace: "305q295892q3ytqiohwhn3tognlewanfe"; nameTypePath: "sampleNameTypePath"; qty: 10000; signature: "sampleSignature", token: "sampleToken" } // sample output CreateNameTypeResponse { success: true; data: { path: "sampleNameTypePath", namespace: "305q295892q3ytqiohwhn3tognlewanfe"; validNameStructureRegex: "/[abc]dd+/g", reservedNames: ["admin", "org"]; displayName: "username"; ttl: 3; purchasePrice: 100; // in cents prePurchasedNameAllotments: [{ namespace: "305q295892q3ytqiohwhn3tognlewanfe"; nameTypePath: "sampleNameTypePath"; qty: 10000; }] }; } ``` - Required Validation: - `Nametype: Update` - for `PrePurchasedNameAllotment` ### Updates #### **update namespace properties** - URI: `.put('/customers/{id}/namespaces/{id}/property')` - input ```typescript // from url // customer {id} - public key of the caller provided by Chaperone // namespace {id} - uuid of the namespace type updateNamespacePropertiesInput = { displayName: string; dnaVersions: string[]; orgDetail: Organization; maintainers: string[]; signature: Signature; // signature to this input that can be verified with the public key } // sample input { displayName: "sampleDisplayName2", dnaVersions: ["sampleDnaVersion2", "sampleDnaVersion3"], orgDetail: { name: "sampleOrgName2", email: "sampleOrgEmail2", country: "sampleOrgCountry2", contactNo: "sampleOrgContactNo2", city: "sampleOrgCity2", province: "sampleOrgProvince2", }, maintainers: ["sampleMaintainer2", "sampleMaintainer3"], signature: "sampleSignature", }; // sample output { success: true; data: { namespace: "305q295892q3ytqiohwhn3tognlewanfe"; displayName: "displayName"; dnaVersions: ["uchgfaewukbglaewjbg", "ahgeeohgboawlbnwglkbga"]; orgDetail: { name: "sampleOrgName", email: "sample@email.com", country: "sampleOrgCountry", contactNo: "sampleOrgContactNo", city: "sampleOrgCity", province: "sampleOrgProvince", }; nameTypes: { "sampleNameTypePath": "sampleNameTypePath" "sampleNameTypePath2": "sampleNameTypePath2" }; nomNomNameType?: "sampleNameTypePath"; orgNomNameType?: "sampleNameTypePath2"; maintainers: ["ucugblewabglknek;aw", "aehwgubawelgblaewknglkaw"]; marketingInfo: { logo?: "geawgewajgnwajlnglawegewa"; appName: "sampleAppName"; appStatus: "Development"; description: "sample description" }; ownershipProof: "aebgfiaewgjbawejlbgjkwebgjlaew"; } } ``` - Required Validation: - `NamespaceProperties: Update` - For updating `NamespaceProperties` #### **update marketing info** - URI: `.put('/customers/{id}/namespaces/{id}/marketing-info')` - input ```typescript // from url // customer {id} - public key of the caller provided by Chaperone // namespace {id} - uuid of the namespace type MarketingInfo = { logo?: string; // base64 of logo appName: string; appStatus: AppStatus; description?: string }; type updateMarketingInfo = MarketingInfo & { signature: Signature; // signature to this input that can be verified with the public key } // sample input { appName: "sampleDescription2", appStatus: "Beta", signature: "sampleSignature", } // sample output { success: true; data: { namespace: "305q295892q3ytqiohwhn3tognlewanfe"; displayName: "displayName"; dnaVersions: ["uchgfaewukbglaewjbg", "ahgeeohgboawlbnwglkbga"]; orgDetail: { name: "sampleOrgName", email: "sample@email.com", country: "sampleOrgCountry", contactNo: "sampleOrgContactNo", city: "sampleOrgCity", province: "sampleOrgProvince", }; nameTypes: { "sampleNameTypePath": "sampleNameTypePath" "sampleNameTypePath2": "sampleNameTypePath2" }; nomNomNameType?: "sampleNameTypePath"; orgNomNameType?: "sampleNameTypePath2"; maintainers: ["ucugblewabglknek;aw", "aehwgubawelgblaewknglkaw"]; marketingInfo: { logo?: "geawgewajgnwajlnglawegewa"; appName: "sampleAppName"; appStatus: "Development"; description: "sample description" }; ownershipProof: "aebgfiaewgjbawejlbgjkwebgjlaew"; }; } ``` - Required Validation: - `NamespaceProperties: Update` - For updating `MarketingInfo` #### **update nametype** - URI: `.put('customers/{id}/namespaces/{id}/nametype/{id}')` - input ```typescript // from url // customer {id} - public key of the caller provided by Chaperone // namespace {id} - uuid of the namespace // nametype {id} - path of the name type type updateNameTypeInput = { displayName: string; ttl: number; // u8 purchasePrice: number; // u32 (in USD cents) signature: Signature; // signature to this input that can be verified with the public key } // sample input { signature: "sampleSignature", displayName: "sampleNameTypeDisplayName2", ttl: 3, purchasePrice: 0, }; // sample output { success: true; data: { path: "sampleNameTypePath", namespace: "305q295892q3ytqiohwhn3tognlewanfe"; validNameStructureRegex: "/[abc]dd+/g", reservedNames: ["admin", "org"]; displayName: "username"; ttl: 3; purchasePrice: 100; // in cents prePurchasedNameAllotments: [{ namespace: "305q295892q3ytqiohwhn3tognlewanfe"; nameTypePath: "sampleNameTypePath"; qty: 10000; }] }; } ``` - Required Validation: - `NameType: Update` ### Reads #### Login API - URI: `.get('/customers/{id})` ```typescript // output type export interface AmIRegisteredResponse { registered: boolean, pubKey: string } // sample output { registered: true, pubKey: "uchae284bglsflekg-gehaowengla" } { registered: false, } ``` #### get all namespaces - URI: `.get('/namespaces')` ```typescript // sample output { "sucess": true, "data":[ { "namespace": { "nomNomRegOptIn":false, "orgNomRegOptIn":false, "ttl":1, "bindingPublicKey": "samplePubKey" }, "properties": { "namespace": "044ad94a76c1808c01bc210027ae340e5aee081677f8c04573bd7ce8e8c461d2", "displayName": "sampleDisplayName", "dnaVersions": ["sampleDnaVersion"], "orgDetail": { "name": "sampleOrgName", "email":"sample@email.com", "country": "sampleOrgCountry", "contactNo": "sampleOrgContactNo", "city":"sampleOrgCity", "province": "sampleOrgProvince" }, "nameTypes": { "sampleNameTypePath": "sampleNameTypePath" }, "maintainers": ["sampleMaintainer"], "marketingInfo": { "logo": "awebgewbglkaewg" "appName":"sampleDescription", "appStatus":"Alpha", "description": "sampleDescription" }, "ownershipProof": "sampleOwnershipProof" }, "nameTypes": { "sampleNameTypePath": { "path": "sampleNameTypePath", "namespace": "044ad94a76c1808c01bc210027ae340e5aee081677f8c04573bd7ce8e8c461d2", "validNameStructureRegex": "/[abc]dd+/g", "reservedNames":["sampleReservedName"], "displayName":"sampleNameTypeDisplayName", "ttl":1, "purchasePrice":100, "prePurchasedNameAllotments": [] } } } ] } ``` #### get all my namespaces - URI: `.get('customers/{id}/namespaces'` - `customers id: public key of the caller provided by Chaperone` ```typescript { "sucess": true, "data":[ { "namespace": { "nomNomRegOptIn":false, "orgNomRegOptIn":false, "ttl":1, "bindingPublicKey": "samplePubKey" }, "properties": { "namespace": "044ad94a76c1808c01bc210027ae340e5aee081677f8c04573bd7ce8e8c461d2", "displayName": "sampleDisplayName", "dnaVersions": ["sampleDnaVersion"], "orgDetail": { "name": "sampleOrgName", "email":"sample@email.com", "country": "sampleOrgCountry", "contactNo": "sampleOrgContactNo", "city":"sampleOrgCity", "province": "sampleOrgProvince" }, "nameTypes": { "sampleNameTypePath": "sampleNameTypePath" }, "maintainers": ["sampleMaintainer"], "marketingInfo": { "logo": "awebgewbglkaewg" "appName":"sampleDescription", "appStatus":"Alpha", "description": "sampleDescription" }, "ownershipProof": "sampleOwnershipProof" }, "nameTypes": { "sampleNameTypePath": { "path": "sampleNameTypePath", "namespace": "044ad94a76c1808c01bc210027ae340e5aee081677f8c04573bd7ce8e8c461d2", "validNameStructureRegex": "/[abc]dd+/g", "reservedNames":["sampleReservedName"], "displayName":"sampleNameTypeDisplayName", "ttl":1, "purchasePrice":100, "prePurchasedNameAllotments": [] } } } ] } ``` #### get namespace - URI: `.get('namespaces/{id}'` - `namespace id: id of the namespace` ```typescript { success: true; data: { namespace: { nomNomRegOptIn: false; orgNomRegOptIn: false; ttl: 1; bindingPublicKey: "uchcAGeahgoowaugtnlekwnt!@3H#T2ipqtn"; }; properties: { namespace: "305q295892q3ytqiohwhn3tognlewanfe"; displayName: "displayName"; dnaVersions: ["uchgfaewukbglaewjbg", "ahgeeohgboawlbnwglkbga"]; orgDetail: { name: "sampleOrgName", email: "sample@email.com", country: "sampleOrgCountry", contactNo: "sampleOrgContactNo", city: "sampleOrgCity", province: "sampleOrgProvince", }; nameTypes: { "sampleNameTypePath": "sampleNameTypePath" "sampleNameTypePath2": "sampleNameTypePath2" }; nomNomNameType?: "sampleNameTypePath"; orgNomNameType?: "sampleNameTypePath2"; maintainers: ["ucugblewabglknek;aw", "aehwgubawelgblaewknglkaw"]; marketingInfo: { logo?: "geawgewajgnwajlnglawegewa"; appName: "sampleAppName"; appStatus: "Development"; description: "sample description" }; ownershipProof: "aebgfiaewgjbawejlbgjkwebgjlaew"; }; nameTypes: { "sampleNameTypePath": { path: "sampleNameTypePath", namespace: "305q295892q3ytqiohwhn3tognlewanfe"; validNameStructureRegex: "/[abc]dd+/g", reservedNames: ["admin", "org"]; displayName: "username"; ttl: 3; purchasePrice: 100; // in cents prePurchasedNameAllotments: [{ namespace: "305q295892q3ytqiohwhn3tognlewanfe"; nameTypePath: "sampleNameTypePath"; qty: 10000; }] }; }; }; } ``` #### *...add more here as needed* ## Types and Validation - Types used in the API ### `Namespace` #### validation - Create - Check that required fields exist in input - `Namespace.NomNomRegOptIn` - `Namespace.OrgNomRegOptIn` - `Namespace.bindingPublicKey` - `Namespace.properties` - check the signature that is in the request and verify it with the binding public key - sign the Namespace using the private key of the creator - if NomNomRegOptIn is true, `nomNomNameType` in `NamespaceProperties` must be defined, and the path should be found as a key in the `nameTypes` object. - Only one NameType can be bound to NomNoms - if OrgNomRegOptIn is true, `orgNomNameType` in `NamespaceProperties` must be defined, and the path should be found as a key in the `nameTypes` object. - - Only one NameType can be bound to OrgNoms - Update: Not valid - Delete: Not valid ```typescript type Namespace = { // originDna?: string, // used as the id of the namespace if present nomNomRegOptIn: boolean; // this is the global name scope for agents orgNomRegOptIn: boolean; // this is the global name scope for organizations ttl: number; // u8 bindingPublicKey: string; }; ``` ### `NamespaceProperties` #### validation - Create - Check the required fields exist in input - `NamespaceProperties.displayName` - `NamespaceProperties.dnaVersions` - `NamespaceProperties.orgDetail` - `orgDetail.name` - `orgDetail.email` - `orgDetail.country` - `orgDetail.city` - `orgDetail.province` - `NamespaceProperties.nameTypes` - `NamespaceProperties.maintainers` - `NamespaceProperties.marketingInfo` - `NamespaceProperties.ownershipProof` - `1 =< NamespaceProperties.displayName >= 50` - `1 =< NamespaceProperties.orgDetail.name >= 50` - 1 =< `appName` => 50 - 0 =< `description` => 300 - Update - check the signature that is in the request and verify it with the binding public key - sign the input using the private key caller and check that it matches one of the public keys in `maintainers` or `bindingPublicKey` - Check required fields exist in input - **For updating `NamespaceProperties`** - `1 =< NamespaceProperties.displayName >= 50` - `1 =< NamespaceProperties.orgDetail.name >= 50` - **For updating `MarketingInfo`** - 1 =< `appName` => 50 - **For updating `Nametypes`** - if previous version had agentNameType id, it must be the same. If not, the agentNameType path must be found in the `nameTypes` array. - if previous version had orgNameType id, it must be the same. If not, the orgNameType path must be found in the `nameTypes` array. - Delete: Not valid ```typescript export type NamespaceProperties = { namespace: string // temporary uuid of namespace which becomes ActionHash later displayName: string; dnaVersions: string[]; orgDetail: Organization; nameTypes: { [nameTypePath: string]: NameType // The key should be replaced with EntryHash later when ported to Holochain. }; nomNomNameType?: string; // the path of the name type bound to nomnom registry. This will be replaced with the EntryHash when ported over to holochain orgNomNameType?: string; // the path of the name type bound to orgnom registry. This will be replaced with the EntryHash when ported over to Holochain. maintainers: string[]; // Agent public keys of maintainers. Set by the owner of namespace. marketingInfo: MarketingInfo; ownershipProof: string; // Option<Signature>. Signature of the owner of namespace. }; export type Organization = { name: string; email: string; country: string; province: string; city: string; contactNo: string; }; export type MarketingInfo = { logo?: string; // base64 of logo appName: string; appStatus: AppStatus: description?: string }; export enum AppStatus { Development, Alpha, Beta, } ``` ### `NameType` #### Validation - Create - required fields must exist - check the signature that is in the request and verify it with the binding public key - sign the input using the private key caller and check that it matches one of the public keys in `maintainers` or `bindingPublicKey` - Regex must conform to a ES6 format - `1 =< displayName >= 50` - `ttl` 0 - `purchasePrice` = 0 if `PrePurchasedNameAllotment` is not empty - `purchasePrice` cannot be 0 and needs to be `purchasePrice` >= 100 (minimum $1) if `PrePurchasedNameAllotment` is empty - `reserved_names` < x - `path` must be unique - for `PrePurchasedNameAllotment` - namespace must be the same id of the namespace as the nameType is part of - nametypePath should match the path of the nameType the PrePurchasedNameAllotment is part of - **Add NameType** - the uuid of the namespace must be the same as the uuid of the namespace it belongs in - Update - check the signature that is in the request and verify it with the binding public key - sign the input using the private key caller and check that it matches one of the public keys in `maintainers` or `bindingPublicKey` - the namespace id provided in the URL must match the namespace id defined in the previous version of the NameType - `1 =< displayName >= 50` - `ttl` < 0 - `purchasePrice` = 0 if `PrePurchasedNameAllotment` is not empty in previous version - `purchasePrice` >= 100 (minimum $1) if previous version of the NameType price > >=100 - **for `PrePurchasedNameAllotment`** - `namespace` must be the same id of the namespace as the nameType is part of - `nametypePath` should match the `path` of the `nameType` the `PrePurchasedNameAllotment` is part of ```typescript export type NameType = { path: string; // immutable(permanent) name of the name type namespace: string; // temporary uuid of the namespace validNameStructureRegex: string; reserved_names: string[], // immutable displayName: string; ttl: number; // u8 purchasePrice: number; // u32 (in USD) PrePurchasedNameAllotments: PrePurchasedNameAllotment[]; // can only be added. Delete is not allowed. }; export type PrePurchasedNameAllotment = { namespace: string; nametypePath: string, qty: number // bundle of free names purchased } ```