UNPKG

@cheqd/sdk

Version:

A TypeScript SDK built with CosmJS to interact with the cheqd network ledger

999 lines 69.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DIDModule = exports.setupDidExtension = exports.typeUrlMsgDeactivateDidDocResponse = exports.typeUrlMsgDeactivateDidDoc = exports.typeUrlMsgUpdateDidDocResponse = exports.typeUrlMsgUpdateDidDoc = exports.typeUrlMsgCreateDidDocResponse = exports.typeUrlMsgCreateDidDoc = exports.protobufLiterals = exports.contexts = exports.defaultDidExtensionKey = void 0; exports.isMsgCreateDidDocEncodeObject = isMsgCreateDidDocEncodeObject; exports.isMsgUpdateDidDocEncodeObject = isMsgUpdateDidDocEncodeObject; exports.isMsgDeactivateDidDocEncodeObject = isMsgDeactivateDidDocEncodeObject; exports.MsgCreateDidDocResponseEncodeObject = MsgCreateDidDocResponseEncodeObject; exports.MsgUpdateDidDocEncodeObject = MsgUpdateDidDocEncodeObject; exports.MsgUpdateDidDocResponseEncodeObject = MsgUpdateDidDocResponseEncodeObject; exports.MsgDeactivateDidDocEncodeObject = MsgDeactivateDidDocEncodeObject; exports.MsgDeactiveDidDocResponseEncodeObject = MsgDeactiveDidDocResponseEncodeObject; const stargate_cjs_1 = require("@cosmjs/stargate-cjs"); const _1 = require("./_"); const types_1 = require("../types"); const index_1 = require("@cheqd/ts-proto-cjs/cheqd/did/v2/index"); const proto_signing_cjs_1 = require("@cosmjs/proto-signing-cjs"); const uuid_cjs_1 = require("uuid-cjs"); const utils_cjs_1 = require("@cosmjs/utils-cjs"); const utils_1 = require("../utils"); const oracle_1 = require("./oracle"); const coin_1 = require("cosmjs-types/cosmos/base/v1beta1/coin"); /** Default extension key for DID-related query operations */ exports.defaultDidExtensionKey = 'did'; /** * Standard W3C and DID-related context URIs used in DID documents. * These contexts define the semantic meaning of properties in DID documents. */ exports.contexts = { /** W3C DID Core v1 context */ W3CDIDv1: 'https://www.w3.org/ns/did/v1', /** Ed25519 Signature Suite 2020 context */ W3CSuiteEd255192020: 'https://w3id.org/security/suites/ed25519-2020/v1', /** Ed25519 Signature Suite 2018 context */ W3CSuiteEd255192018: 'https://w3id.org/security/suites/ed25519-2018/v1', /** JSON Web Signature Suite 2020 context */ W3CSuiteJws2020: 'https://w3id.org/security/suites/jws-2020/v1', /** Linked Domains context for domain verification */ LinkedDomainsContext: 'https://identity.foundation/.well-known/did-configuration/v1', }; /** * Protobuf message type literals for DID operations. * Used for consistent message type identification across the module. */ exports.protobufLiterals = { /** Create DID document message type */ MsgCreateDidDoc: 'MsgCreateDidDoc', /** Create DID document response message type */ MsgCreateDidDocResponse: 'MsgCreateDidDocResponse', /** Update DID document message type */ MsgUpdateDidDoc: 'MsgUpdateDidDoc', /** Update DID document response message type */ MsgUpdateDidDocResponse: 'MsgUpdateDidDocResponse', /** Deactivate DID document message type */ MsgDeactivateDidDoc: 'MsgDeactivateDidDoc', /** Deactivate DID document response message type */ MsgDeactivateDidDocResponse: 'MsgDeactivateDidDocResponse', }; /** Type URL for MsgCreateDidDoc messages */ exports.typeUrlMsgCreateDidDoc = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgCreateDidDoc}`; /** Type URL for MsgCreateDidDocResponse messages */ exports.typeUrlMsgCreateDidDocResponse = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgCreateDidDocResponse}`; /** Type URL for MsgUpdateDidDoc messages */ exports.typeUrlMsgUpdateDidDoc = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgUpdateDidDoc}`; /** Type URL for MsgUpdateDidDocResponse messages */ exports.typeUrlMsgUpdateDidDocResponse = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgUpdateDidDocResponse}`; /** Type URL for MsgDeactivateDidDoc messages */ exports.typeUrlMsgDeactivateDidDoc = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgDeactivateDidDoc}`; /** Type URL for MsgDeactivateDidDocResponse messages */ exports.typeUrlMsgDeactivateDidDocResponse = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgDeactivateDidDocResponse}`; /** * Type guard function to check if an object is a MsgCreateDidDocEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgCreateDidDocEncodeObject */ function isMsgCreateDidDocEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgCreateDidDoc; } /** * Type guard function to check if an object is a MsgUpdateDidDocEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgUpdateDidDocEncodeObject */ function isMsgUpdateDidDocEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgUpdateDidDoc; } /** * Type guard function to check if an object is a MsgDeactivateDidDocEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgDeactivateDidDocEncodeObject */ function isMsgDeactivateDidDocEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgDeactivateDidDoc; } /** * Type guard function to check if an object is a MsgCreateDidDocResponseEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgCreateDidDocResponseEncodeObject */ function MsgCreateDidDocResponseEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgCreateDidDocResponse; } /** * Type guard function to check if an object is a MsgUpdateDidDocEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgUpdateDidDocEncodeObject */ function MsgUpdateDidDocEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgUpdateDidDoc; } /** * Type guard function to check if an object is a MsgUpdateDidDocResponseEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgUpdateDidDocResponseEncodeObject */ function MsgUpdateDidDocResponseEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgUpdateDidDocResponse; } /** * Type guard function to check if an object is a MsgDeactivateDidDocEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgDeactivateDidDocEncodeObject */ function MsgDeactivateDidDocEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgDeactivateDidDoc; } /** * Type guard function to check if an object is a MsgDeactivateDidDocResponseEncodeObject. * * @param obj - EncodeObject to check * @returns True if the object is a MsgDeactivateDidDocResponseEncodeObject */ function MsgDeactiveDidDocResponseEncodeObject(obj) { return obj.typeUrl === exports.typeUrlMsgUpdateDidDocResponse; } /** * Sets up the DID extension for the querier client. * Creates and configures the DID-specific query methods. * * @param base - Base QueryClient to extend * @returns Configured DID extension with query methods */ const setupDidExtension = (base) => { const rpc = (0, stargate_cjs_1.createProtobufRpcClient)(base); const queryService = new index_1.QueryClientImpl(rpc); return { [exports.defaultDidExtensionKey]: { didDoc: async (id) => { const { value } = await queryService.DidDoc({ id }); (0, utils_cjs_1.assert)(value); return value; }, didDocVersion: async (id, versionId) => { const { value } = await queryService.DidDocVersion({ id, version: versionId }); (0, utils_cjs_1.assert)(value); return value; }, allDidDocVersionsMetadata: async (id, paginationKey) => { const response = await queryService.AllDidDocVersionsMetadata({ id, pagination: (0, stargate_cjs_1.createPagination)(paginationKey), }); return response; }, params: async () => { const response = await queryService.Params({}); (0, utils_cjs_1.assert)(response.params); return response; }, }, }; }; exports.setupDidExtension = setupDidExtension; /** * DID Module class providing comprehensive DID document management functionality. * Handles creation, updates, deactivation, and querying of DID documents on the Cheqd blockchain. */ class DIDModule extends _1.AbstractCheqdSDKModule { // @ts-expect-error underlying type `GeneratedType` is intentionally wider static registryTypes = [ [exports.typeUrlMsgCreateDidDoc, index_1.MsgCreateDidDoc], [exports.typeUrlMsgCreateDidDocResponse, index_1.MsgCreateDidDocResponse], [exports.typeUrlMsgUpdateDidDoc, index_1.MsgUpdateDidDoc], [exports.typeUrlMsgUpdateDidDocResponse, index_1.MsgUpdateDidDocResponse], [exports.typeUrlMsgDeactivateDidDoc, index_1.MsgDeactivateDidDoc], [exports.typeUrlMsgDeactivateDidDocResponse, index_1.MsgDeactivateDidDocResponse], ]; /** Base denomination for Cheqd network transactions */ static baseMinimalDenom = 'ncheq'; /** Base denomination in USD for Cheqd network transactions */ static baseUsdDenom = 'usd'; /** Default slippage tolerance in base points (BPS) */ static defaultSlippageBps = 500n; /** * Standard fee amounts for DID operations. * These represent the default costs for different DID document operations. */ static fees = { /** Default fee for creating a new DID document */ DefaultCreateDidDocFee: { amount: '50000000000', denom: DIDModule.baseMinimalDenom }, /** Default fee for updating an existing DID document */ DefaultUpdateDidDocFee: { amount: '25000000000', denom: DIDModule.baseMinimalDenom }, /** Default fee for deactivating a DID document */ DefaultDeactivateDidDocFee: { amount: '10000000000', denom: DIDModule.baseMinimalDenom }, /** Default fee for creating a new DID document in USD */ DefaultCreateDidDocFeeUSD: { amount: '2000000000000000000', denom: DIDModule.baseUsdDenom }, /** Default fee for updating an existing DID document in USD */ DefaultUpdateDidDocFeeUSD: { amount: '1000000000000000000', denom: DIDModule.baseUsdDenom }, /** Default fee for deactivating a DID document in USD */ DefaultDeactivateDidDocFeeUSD: { amount: '400000000000000000', denom: DIDModule.baseUsdDenom }, }; /** * Standard gas limits for DID operations. * These represent the default gas limits for different DID document operations. */ static gasLimits = { /** Gas limit for creating a new DID document */ CreateDidDocGasLimit: '360000', /** Gas limit for updating an existing DID document */ UpdateDidDocGasLimit: '360000', /** Gas limit for deactivating a DID document */ DeactivateDidDocGasLimit: '360000', }; /** Querier extension setup function for DID operations */ static querierExtensionSetup = exports.setupDidExtension; /** Querier instance with DID extension capabilities */ querier; oracleFeesAvailability; /** * Constructs a new DID module instance. * * @param signer - Signing client for blockchain transactions * @param querier - Querier client with DID extension for data retrieval */ constructor(signer, querier) { super(signer, querier); this.querier = querier; this.methods = { createDidDocTx: this.createDidDocTx.bind(this), updateDidDocTx: this.updateDidDocTx.bind(this), deactivateDidDocTx: this.deactivateDidDocTx.bind(this), queryDidDoc: this.queryDidDoc.bind(this), queryDidDocVersion: this.queryDidDocVersion.bind(this), queryAllDidDocVersionsMetadata: this.queryAllDidDocVersionsMetadata.bind(this), queryDidParams: this.queryDidParams.bind(this), generateCreateDidDocFees: this.generateCreateDidDocFees.bind(this), }; } /** * Gets the registry types for DID message encoding/decoding. * * @returns Iterable of [typeUrl, GeneratedType] pairs for the registry */ getRegistryTypes() { return DIDModule.registryTypes; } /** * Gets the querier extension setup for DID operations. * * @returns Query extension setup function for DID functionality */ getQuerierExtensionSetup() { return DIDModule.querierExtensionSetup; } /** * Creates a new DID document transaction on the blockchain. * Validates the DID payload and authentication before submission. * * @param signInputs - Signing inputs or pre-computed signatures for the transaction * @param didPayload - DID document payload to create * @param address - Address of the account submitting the transaction * @param fee - Transaction fee configuration or 'auto' for automatic calculation * @param memo - Optional transaction memo * @param versionId - Optional version identifier for the DID document * @param feeOptions - Optional fee options for the transaction * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the transaction response * @throws Error if DID payload is not spec compliant or authentication is invalid */ async createDidDocTx(signInputs, didPayload, address, fee, memo, versionId, feeOptions, context) { if (!this._signer) { this._signer = context.sdk.signer; } if (!this.querier) { this.querier = context.sdk.querier; } if (!versionId || versionId === '') { versionId = (0, uuid_cjs_1.v4)(); } const { valid, error, protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload); if (!valid) { throw new Error(`DID payload is not spec compliant: ${error}`); } const { valid: authenticationValid, error: authenticationError } = await DIDModule.validateAuthenticationAgainstSignatures(didPayload, signInputs, this.querier); if (!authenticationValid) { throw new Error(`DID authentication is not valid: ${authenticationError}`); } const payload = index_1.MsgCreateDidDocPayload.fromPartial({ context: didPayload?.['@context'], id: didPayload.id, controller: didPayload.controller, verificationMethod: protobufVerificationMethod, authentication: didPayload.authentication, assertionMethod: didPayload.assertionMethod, capabilityInvocation: didPayload.capabilityInvocation, capabilityDelegation: didPayload.capabilityDelegation, keyAgreement: didPayload.keyAgreement, service: protobufService, alsoKnownAs: didPayload.alsoKnownAs, versionId: versionId, }); let signatures; if (types_1.ISignInputs.isSignInput(signInputs)) { signatures = await this._signer.signCreateDidDocTx(signInputs, payload); } else { signatures = signInputs; } const value = { payload, signatures, }; const createDidMsg = { typeUrl: exports.typeUrlMsgCreateDidDoc, value, }; if (address === '') { address = (await context.sdk.options.wallet.getAccounts())[0].address; } if (!fee) { fee = await this.generateCreateDidDocFees(address, undefined, feeOptions, context); } return this._signer.signAndBroadcast(address, [createDidMsg], fee, memo); } /** * Updates an existing DID document transaction on the blockchain. * Validates the updated DID payload and handles key rotation scenarios. * * @param signInputs - Signing inputs or pre-computed signatures for the transaction * @param didPayload - Updated DID document payload * @param address - Address of the account submitting the transaction * @param fee - Transaction fee configuration or 'auto' for automatic calculation * @param memo - Optional transaction memo * @param versionId - Optional version identifier for the updated DID document * @param feeOptions - Optional fee options for the transaction * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the transaction response * @throws Error if DID payload is not spec compliant or authentication is invalid */ async updateDidDocTx(signInputs, didPayload, address, fee, memo, versionId, feeOptions, context) { if (!this._signer) { this._signer = context.sdk.signer; } if (!this.querier) { this.querier = context.sdk.querier; } if (!versionId || versionId === '') { versionId = (0, uuid_cjs_1.v4)(); } const { valid, error, protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload); if (!valid) { throw new Error(`DID payload is not spec compliant: ${error}`); } const { valid: authenticationValid, error: authenticationError, externalControllersDocuments, previousDidDocument, } = await DIDModule.validateAuthenticationAgainstSignaturesKeyRotation(didPayload, signInputs, this.querier); if (!authenticationValid) { throw new Error(`DID authentication is not valid: ${authenticationError}`); } const payload = index_1.MsgUpdateDidDocPayload.fromPartial({ context: didPayload?.['@context'], id: didPayload.id, controller: didPayload.controller, verificationMethod: protobufVerificationMethod, authentication: didPayload.authentication, assertionMethod: didPayload.assertionMethod, capabilityInvocation: didPayload.capabilityInvocation, capabilityDelegation: didPayload.capabilityDelegation, keyAgreement: didPayload.keyAgreement, service: protobufService, alsoKnownAs: didPayload.alsoKnownAs, versionId: versionId, }); let signatures; if (types_1.ISignInputs.isSignInput(signInputs)) { signatures = await this._signer.signUpdateDidDocTx(signInputs, payload, externalControllersDocuments, previousDidDocument); } else { signatures = signInputs; } const value = { payload, signatures, }; const updateDidMsg = { typeUrl: exports.typeUrlMsgUpdateDidDoc, value, }; if (address === '') { address = (await context.sdk.options.wallet.getAccounts())[0].address; } if (!fee) { fee = await this.generateUpdateDidDocFees(address, undefined, feeOptions, context); } return this._signer.signAndBroadcast(address, [updateDidMsg], fee, memo); } /** * Deactivates an existing DID document transaction on the blockchain. * Validates authentication and creates a deactivation transaction. * * @param signInputs - Signing inputs or pre-computed signatures for the transaction * @param didPayload - DID document payload containing the ID to deactivate * @param address - Address of the account submitting the transaction * @param fee - Transaction fee configuration or 'auto' for automatic calculation * @param memo - Optional transaction memo * @param versionId - Optional version identifier for the deactivation * @param feeOptions - Optional fee options for the transaction * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the transaction response * @throws Error if DID payload is not spec compliant or authentication is invalid */ async deactivateDidDocTx(signInputs, didPayload, address, fee, memo, versionId, feeOptions, context) { if (!this._signer) { this._signer = context.sdk.signer; } if (!versionId || versionId === '') { versionId = (0, uuid_cjs_1.v4)(); } const { valid, error, protobufVerificationMethod } = await DIDModule.validateSpecCompliantPayload(didPayload); if (!valid) { throw new Error(`DID payload is not spec compliant: ${error}`); } const { valid: authenticationValid, error: authenticationError } = await DIDModule.validateAuthenticationAgainstSignatures(didPayload, signInputs, this.querier); if (!authenticationValid) { throw new Error(`DID authentication is not valid: ${authenticationError}`); } const payload = index_1.MsgDeactivateDidDocPayload.fromPartial({ id: didPayload.id, versionId: versionId, }); let signatures; if (types_1.ISignInputs.isSignInput(signInputs)) { signatures = await this._signer.signDeactivateDidDocTx(signInputs, payload, protobufVerificationMethod); } else { signatures = signInputs; } const value = { payload, signatures, }; const deactivateDidMsg = { typeUrl: exports.typeUrlMsgDeactivateDidDoc, value, }; if (address === '') { address = (await context.sdk.options.wallet.getAccounts())[0].address; } if (!fee) { fee = await this.generateDeactivateDidDocFees(address, undefined, feeOptions, context); } return this._signer.signAndBroadcast(address, [deactivateDidMsg], fee, memo); } /** * Queries a DID document by its identifier. * Retrieves the latest version of the DID document with metadata. * * @param id - DID identifier to query * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the DID document with metadata */ async queryDidDoc(id, context) { if (!this.querier) { this.querier = context.sdk.querier; } const { didDoc, metadata } = await this.querier[exports.defaultDidExtensionKey].didDoc(id); return { didDocument: await DIDModule.toSpecCompliantPayload(didDoc), didDocumentMetadata: await DIDModule.toSpecCompliantMetadata(metadata), }; } /** * Queries a specific version of a DID document by its identifier and version ID. * * @param id - DID identifier to query * @param versionId - Specific version identifier to retrieve * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the DID document version with metadata */ async queryDidDocVersion(id, versionId, context) { if (!this.querier) { this.querier = context.sdk.querier; } const { didDoc, metadata } = await this.querier[exports.defaultDidExtensionKey].didDocVersion(id, versionId); return { didDocument: await DIDModule.toSpecCompliantPayload(didDoc), didDocumentMetadata: await DIDModule.toSpecCompliantMetadata(metadata), }; } /** * Queries metadata for all versions of a DID document. * Retrieves version history information for a specific DID. * * @param id - DID identifier to query version metadata for * @param context - Optional SDK context for accessing clients * @returns Promise resolving to array of version metadata and pagination info */ async queryAllDidDocVersionsMetadata(id, context) { if (!this.querier) { this.querier = context.sdk.querier; } const { versions, pagination } = await this.querier[exports.defaultDidExtensionKey].allDidDocVersionsMetadata(id); return { didDocumentVersionsMetadata: await Promise.all(versions.map(async (m) => await DIDModule.toSpecCompliantMetadata(m))), pagination, }; } async shouldUseOracleFees(context) { if (!this.oracleFeesAvailability) { this.oracleFeesAvailability = (async () => { if (!this.querier && context?.sdk?.querier) { this.querier = context.sdk.querier; } const oracle = this.querier?.[oracle_1.defaultOracleExtensionKey]; if (!oracle?.queryParams) { return false; } try { await oracle.queryParams(); return true; } catch { return false; } })(); } return this.oracleFeesAvailability; } /** * Queries the DID module parameters from the blockchain. * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the DID module parameters */ async queryDidParams(context) { if (!this.querier) { this.querier = context.sdk.querier; } return this.querier[exports.defaultDidExtensionKey].params(); } /** * Generates oracle-powered fees for creating a DID document. * * @param feePayer - Address of the account that will pay the transaction fees * @param granter - Optional address of the account granting fee payment permissions * @param feeOptions - Options for fetching oracle fees * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the fee configuration for DID document creation with oracle fees */ async generateCreateDidDocFees(feePayer, granter, feeOptions, context) { if (!this.querier) { this.querier = context.sdk.querier; } if (!(await this.shouldUseOracleFees(context))) { return DIDModule.generateCreateDidDocFees(feePayer, granter); } // fetch fee parameters from the DID module const feeParams = await this.queryDidParams(context); // get the price range for the create operation const priceRange = await this.getPriceRangeFromDidParams(feeParams, 'create', feeOptions); // calculate the oracle fee amount based on the price range and options return { amount: [await this.calculateOracleFeeAmount(priceRange, feeOptions, context)], gas: feeOptions?.gasLimit || DIDModule.gasLimits.CreateDidDocGasLimit, payer: feePayer, granter, }; } /** * Generates oracle-powered fees for updating a DID document. * * @param feePayer - Address of the account that will pay the transaction fees * @param granter - Optional address of the account granting fee payment permissions * @param feeOptions - Options for fetching oracle fees * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the fee configuration for DID document update with oracle fees */ async generateUpdateDidDocFees(feePayer, granter, fetchOptions, context) { if (!this.querier) { this.querier = context.sdk.querier; } if (!(await this.shouldUseOracleFees(context))) { return DIDModule.generateUpdateDidDocFees(feePayer, granter); } // fetch fee parameters from the DID module const feeParams = await this.queryDidParams(context); // get the price range for the update operation const priceRange = await this.getPriceRangeFromDidParams(feeParams, 'update', fetchOptions); // calculate the oracle fee amount based on the price range and options return { amount: [await this.calculateOracleFeeAmount(priceRange, fetchOptions, context)], gas: fetchOptions?.gasLimit || DIDModule.gasLimits.UpdateDidDocGasLimit, payer: feePayer, granter, }; } /** Generates oracle-powered fees for deactivating a DID document. * * @param feePayer - Address of the account that will pay the transaction fees * @param granter - Optional address of the account granting fee payment permissions * @param feeOptions - Options for fetching oracle fees * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the fee configuration for DID document deactivation with oracle fees */ async generateDeactivateDidDocFees(feePayer, granter, feeOptions, context) { if (!this.querier) { this.querier = context.sdk.querier; } if (!(await this.shouldUseOracleFees(context))) { return DIDModule.generateDeactivateDidDocFees(feePayer, granter); } // fetch fee parameters from the DID module const feeParams = await this.queryDidParams(context); // get the price range for the deactivate operation const priceRange = await this.getPriceRangeFromDidParams(feeParams, 'deactivate', feeOptions); // calculate the oracle fee amount based on the price range and options return { amount: [await this.calculateOracleFeeAmount(priceRange, feeOptions, context)], gas: feeOptions?.gasLimit || DIDModule.gasLimits.DeactivateDidDocGasLimit, payer: feePayer, granter, }; } /** * Gets the fee range for a specific DID operation from the module parameters. * @param feeParams - DID module fee parameters * @param operation - DID operation type ('create', 'update', 'deactivate') * @param feeOptions - Options for fee retrieval * @returns Promise resolving to the fee range for the specified operation */ async getPriceRangeFromDidParams(feeParams, operation, feeOptions) { const operationFees = (() => { switch (operation) { case 'create': return feeParams.params?.createDid; case 'update': return feeParams.params?.updateDid; case 'deactivate': return feeParams.params?.deactivateDid; default: throw new Error('Unsupported operation for fee retrieval'); } })(); const operationFee = feeOptions?.feeDenom ? operationFees?.find((fee) => fee.denom === feeOptions.feeDenom) : (operationFees?.find((fee) => fee.denom === DIDModule.baseUsdDenom) ?? operationFees?.find((fee) => fee.denom === DIDModule.baseMinimalDenom)); if (!operationFee) { throw new Error(`Fee parameters not found for operation: ${operation}`); } return operationFee; } /** * Calculates the oracle fee amount based on the provided fee range and options. * @param feeRange - Fee range for the DID operation * @param feeOptions - Options for fee calculation * @param context - Optional SDK context for accessing clients * @returns Promise resolving to the calculated fee amount as a Coin */ async calculateOracleFeeAmount(feeRange, feeOptions, context) { if (!this.querier) { this.querier = context.sdk.querier; } if (feeRange.denom !== feeOptions?.feeDenom && feeOptions?.feeDenom !== undefined) { throw new Error(`Fee denomination mismatch: expected ${feeRange.denom}, got ${feeOptions.feeDenom}`); } const wantedFeeAmount = feeRange.denom === DIDModule.baseUsdDenom ? (feeOptions?.wantedAmountUsd ?? DIDModule.isFixedRange(feeRange)) ? feeRange.minAmount : feeRange.minAmount : feeRange.minAmount; // override fee options, if unassigned - case: moving average type feeOptions = { ...feeOptions, movingAverageType: feeOptions?.movingAverageType || oracle_1.MovingAverages.WMA, }; // override fee options, if unassigned - case: WMA strategy feeOptions = { ...feeOptions, wmaStrategy: feeOptions?.wmaStrategy || feeOptions?.movingAverageType === oracle_1.MovingAverages.WMA ? oracle_1.WMAStrategies.BALANCED : undefined, }; const convertedFeeAmount = feeRange.denom === DIDModule.baseUsdDenom ? (0, proto_signing_cjs_1.parseCoins)((await this.querier[oracle_1.defaultOracleExtensionKey].convertUSDtoCHEQ(wantedFeeAmount, feeOptions?.movingAverageType, feeOptions?.wmaStrategy, feeOptions?.wmaWeights?.map((w) => BigInt(w)))).amount)[0] : coin_1.Coin.fromPartial({ amount: wantedFeeAmount, denom: feeRange.denom }); return feeOptions?.slippageBps ? DIDModule.applySlippageToCoin(convertedFeeAmount, feeOptions.slippageBps) : convertedFeeAmount; } /** * Applies slippage to a given coin amount based on the specified basis points. * @param coin - Coin amount to apply slippage to * @param slippageBps - Slippage in basis points (bps) * @returns Coin with adjusted amount after applying slippage */ static applySlippageToCoin(coin, slippageBps) { const base = BigInt(coin.amount); const delta = (base * BigInt(slippageBps)) / BigInt(10_000); const adjustedAmount = base + delta; return coin_1.Coin.fromPartial({ amount: adjustedAmount.toString(), denom: coin.denom }); } /** * Checks if a fee range represents a fixed fee (minAmount equals maxAmount). * @param feeRange - Fee range to check * @returns True if the fee range is fixed, false otherwise */ static isFixedRange(feeRange) { return feeRange.minAmount === feeRange.maxAmount; } /** * Validates a DID document against the Cheqd specification requirements. * Ensures all required fields are present and verification methods are supported. * * @param didDocument - DID document to validate * @returns Promise resolving to validation result with protobuf conversion or error details */ static async validateSpecCompliantPayload(didDocument) { // id is required, validated on both compile and runtime if (!didDocument?.id) return { valid: false, error: 'id is required' }; // verificationMethod is required if (!didDocument?.verificationMethod) return { valid: false, error: 'verificationMethod is required' }; // verificationMethod must be an array if (!Array.isArray(didDocument?.verificationMethod)) return { valid: false, error: 'verificationMethod must be an array' }; // verificationMethod types must be supported const protoVerificationMethod = didDocument.verificationMethod.map((vm) => { switch (vm?.type) { case types_1.VerificationMethods.Ed255192020: if (!vm?.publicKeyMultibase) throw new Error('publicKeyMultibase is required'); return index_1.VerificationMethod.fromPartial({ id: vm.id, controller: vm.controller, verificationMethodType: types_1.VerificationMethods.Ed255192020, verificationMaterial: vm.publicKeyMultibase, }); case types_1.VerificationMethods.JWK: if (!vm?.publicKeyJwk) throw new Error('publicKeyJwk is required'); return index_1.VerificationMethod.fromPartial({ id: vm.id, controller: vm.controller, verificationMethodType: types_1.VerificationMethods.JWK, verificationMaterial: JSON.stringify(vm.publicKeyJwk), }); case types_1.VerificationMethods.Ed255192018: if (!vm?.publicKeyBase58) throw new Error('publicKeyBase58 is required'); return index_1.VerificationMethod.fromPartial({ id: vm.id, controller: vm.controller, verificationMethodType: types_1.VerificationMethods.Ed255192018, verificationMaterial: vm.publicKeyBase58, }); default: throw new Error('Unsupported verificationMethod type'); } }); const protoService = (0, utils_1.normalizeService)(didDocument); return { valid: true, protobufVerificationMethod: protoVerificationMethod, protobufService: protoService, }; } /** * Converts a protobuf DID document to a specification-compliant DID document format. * Handles context inclusion, verification method formatting, and service denormalization. * * @param protobufDidDocument - Protobuf DID document to convert * @returns Promise resolving to a spec-compliant DID document */ static async toSpecCompliantPayload(protobufDidDocument) { const verificationMethod = protobufDidDocument.verificationMethod.map((vm) => { switch (vm.verificationMethodType) { case types_1.VerificationMethods.Ed255192020: if (!protobufDidDocument.context.includes(exports.contexts.W3CSuiteEd255192020)) protobufDidDocument.context = [...protobufDidDocument.context, exports.contexts.W3CSuiteEd255192020]; return { id: vm.id, type: vm.verificationMethodType, controller: vm.controller, publicKeyMultibase: vm.verificationMaterial, }; case types_1.VerificationMethods.JWK: if (!protobufDidDocument.context.includes(exports.contexts.W3CSuiteJws2020)) protobufDidDocument.context = [...protobufDidDocument.context, exports.contexts.W3CSuiteJws2020]; return { id: vm.id, type: vm.verificationMethodType, controller: vm.controller, publicKeyJwk: JSON.parse(vm.verificationMaterial), }; case types_1.VerificationMethods.Ed255192018: if (!protobufDidDocument.context.includes(exports.contexts.W3CSuiteEd255192018)) protobufDidDocument.context = [...protobufDidDocument.context, exports.contexts.W3CSuiteEd255192018]; return { id: vm.id, type: vm.verificationMethodType, controller: vm.controller, publicKeyBase58: vm.verificationMaterial, }; default: throw new Error('Unsupported verificationMethod type'); // should never happen } }); const service = (0, utils_1.denormalizeService)(protobufDidDocument); const context = (function () { if (protobufDidDocument.context.includes(exports.contexts.W3CDIDv1)) return protobufDidDocument.context; return [exports.contexts.W3CDIDv1, ...protobufDidDocument.context]; })(); const assertionMethod = protobufDidDocument.assertionMethod.map((am) => { try { // Check if the assertionMethod is a DID URL if (!am.startsWith('did:cheqd:')) { // Parse once if it's a stringified JSON const parsedAm = JSON.parse(am); if (typeof parsedAm === 'string') { // Parse again only if necessary return JSON.parse(parsedAm); } return parsedAm; } return am; } catch (error) { throw new Error(`Unsupported assertionMethod type: ${am}`); } }); const specCompliant = { '@context': context, id: protobufDidDocument.id, controller: protobufDidDocument.controller, verificationMethod: verificationMethod, authentication: protobufDidDocument.authentication, assertionMethod: assertionMethod, capabilityInvocation: protobufDidDocument.capabilityInvocation, capabilityDelegation: protobufDidDocument.capabilityDelegation, keyAgreement: protobufDidDocument.keyAgreement, service: service, alsoKnownAs: protobufDidDocument.alsoKnownAs, }; if (!protobufDidDocument.authentication?.length) delete specCompliant.authentication; if (!protobufDidDocument.assertionMethod?.length) delete specCompliant.assertionMethod; if (!protobufDidDocument.capabilityInvocation?.length) delete specCompliant.capabilityInvocation; if (!protobufDidDocument.capabilityDelegation?.length) delete specCompliant.capabilityDelegation; if (!protobufDidDocument.keyAgreement?.length) delete specCompliant.keyAgreement; if (!protobufDidDocument.service?.length) delete specCompliant.service; if (!protobufDidDocument.alsoKnownAs?.length) delete specCompliant.alsoKnownAs; return specCompliant; } /** * Converts protobuf metadata to specification-compliant DID document metadata format. * Handles date formatting and optional field normalization. * * @param protobufDidDocument - Protobuf metadata to convert * @returns Promise resolving to spec-compliant DID document metadata */ static async toSpecCompliantMetadata(protobufDidDocument) { return { created: protobufDidDocument.created?.toISOString(), updated: protobufDidDocument.updated?.toISOString(), deactivated: protobufDidDocument.deactivated, versionId: protobufDidDocument.versionId, nextVersionId: protobufDidDocument?.nextVersionId, previousVersionId: protobufDidDocument?.previousVersionId, }; } /** * Validates that provided signatures match the authentication requirements in a DID document. * Checks signature count, authentication presence, and controller authorization. * * @param didDocument - DID document containing authentication requirements * @param signatures - Array of signatures to validate against authentication * @param querier - Optional querier for retrieving external controller documents * @param externalControllersDidDocuments - Optional pre-loaded external controller documents * @returns Promise resolving to validation result with error details if invalid */ static async validateAuthenticationAgainstSignatures(didDocument, signatures, querier, externalControllersDidDocuments) { // validate signatures - case: no signatures if (!signatures || !signatures.length) return { valid: false, error: 'signatures are required' }; // validate authentication - case: no authentication when at least one verificationMethod if ((!didDocument.authentication || !didDocument.authentication.length) && didDocument.verificationMethod?.length) return { valid: false, error: 'authentication is required' }; const normalizedAuthentication = (0, utils_1.normalizeAuthentication)(didDocument); // define unique authentication const uniqueAuthentication = new Set(normalizedAuthentication); // validate authentication - case: authentication contains duplicates if (uniqueAuthentication.size < normalizedAuthentication.length) return { valid: false, error: `authentication contains duplicate key references: duplicate key reference ${Array.from(uniqueAuthentication).find((a) => (0, utils_1.normalizeAuthentication)(didDocument).filter((aa) => aa === a).length > 1)}`, }; // define unique signatures - shallow, only verificationMethodId, no signature const uniqueSignatures = new Set(signatures.map((s) => s.verificationMethodId)); // validate signatures - case: signatures contain duplicates if (uniqueSignatures.size < signatures.length) return { valid: false, error: `signatures contain duplicates: duplicate signature for key reference ${Array.from(uniqueSignatures).find((s) => signatures.filter((ss) => ss.verificationMethodId === s).length > 1)}`, }; // validate authentication - case: authentication contains invalid key references if (!Array.from(uniqueAuthentication).every((a) => didDocument.verificationMethod?.some((vm) => vm.id === a))) return { valid: false, error: `authentication contains invalid key references: invalid key reference ${Array.from(uniqueAuthentication).find((a) => !didDocument.verificationMethod?.some((vm) => vm.id === a))}`, }; // define whether external controller or not const externalController = (0, utils_1.normalizeController)(didDocument).some((c) => c !== didDocument.id); // validate authentication - case: authentication matches signatures, unique, if no external controller if (!Array.from(uniqueAuthentication).every((a) => uniqueSignatures.has(a)) && !externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueAuthentication).find((a) => !uniqueSignatures.has(a))} is missing`, }; // validate signatures - case: authentication matches signatures, unique, excessive signatures, no external controller if (!Array.from(uniqueSignatures).every((s) => uniqueAuthentication.has(s)) && !externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignatures).find((s) => !uniqueAuthentication.has(s))} is not required`, }; // return, if no external controller if (!externalController) return { valid: true }; // require querier if (!querier) throw new Error('querier is required for external controller validation'); // get external controllers const externalControllers = (0, utils_1.normalizeController)(didDocument).filter((c) => c !== didDocument.id); // get external controllers' documents const externalControllersDocuments = await Promise.all(externalControllers?.map(async (c) => { // compute index of external controller's document, if provided const externalControllerDocumentIndex = externalControllersDidDocuments?.findIndex((d) => d.id === c); // get external controller's document, if provided if (externalControllerDocumentIndex !== undefined && externalControllerDocumentIndex !== -1) return externalControllersDidDocuments?.[externalControllerDocumentIndex]; // fetch external controller's document const protobufDocument = await querier[exports.defaultDidExtensionKey].didDoc(c); // throw, if not found if (!protobufDocument || !protobufDocument.didDoc) throw new Error(`Document for controller ${c} not found`); // throw, if deactivated if (protobufDocument.metadata?.deactivated === true) throw new Error(`Controller ${c} is deactivated`); // convert to spec compliant payload return await DIDModule.toSpecCompliantPayload(protobufDocument.didDoc); })); // define unique required signatures const uniqueRequiredSignatures = new Set(externalControllersDocuments.concat(didDocument).flatMap((d) => (d ? (0, utils_1.normalizeAuthentication)(d) : []))); // validate authentication - case: authentication matches signatures, unique, if external controller if (!Array.from(uniqueRequiredSignatures).every((a) => uniqueSignatures.has(a))) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueRequiredSignatures).find((a) => !uniqueSignatures.has(a))} is missing`, }; // validate authentication - case: authentication matches signatures, unique, excessive signatures, if external controller if (uniqueRequiredSignatures.size < uniqueSignatures.size) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignatures).find((s) => !uniqueRequiredSignatures.has(s))} is not required`, }; // return valid return { valid: true }; } /** * Validates authentication against signatures for key rotation scenarios. * Handles validation during DID document updates where keys may have changed. * * @param didDocument - Updated DID document to validate * @param signatures - Array of signatures to validate * @param querier - Querier for retrieving previous DID document and controllers * @param previousDidDocument - Optional previous version of the DID document * @param externalControllersDid