@cheqd/sdk
Version:
A TypeScript SDK built with CosmJS to interact with the cheqd network ledger
541 lines • 22.8 kB
JavaScript
import { VerificationMethods, MethodSpecificIdAlgo, CheqdNetwork, ServiceType, } from './types.js';
import { fromString, toString } from 'uint8arrays';
import { bases } from 'multiformats/basics';
import { base64ToBytes } from 'did-jwt';
import { generateKeyPair, generateKeyPairFromSeed } from '@stablelib/ed25519';
import { DirectSecp256k1HdWallet, DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
import { EnglishMnemonic as _, sha256 } from '@cosmjs/crypto';
import { rawSecp256k1PubkeyToRawAddress } from '@cosmjs/amino';
import pkg from 'secp256k1';
import { v4 } from 'uuid';
import { VerificationMethod as ProtoVerificationMethod, Service as ProtoService, MsgCreateDidDocPayload, MsgDeactivateDidDocPayload, } from '@cheqd/ts-proto/cheqd/did/v2/index.js';
import { contexts, DIDModule } from './modules/did.js';
import { MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2/index.js';
import { toBech32 } from '@cosmjs/encoding';
import { StargateClient } from '@cosmjs/stargate';
import { backOff } from 'exponential-backoff';
/**
* Utility object for validating TImportableEd25519Key objects.
* Provides type guard functionality to ensure key structure integrity.
*/
export const TImportableEd25519Key = {
/**
* Type guard to validate if an object is a valid TImportableEd25519Key.
*
* @param key - Object to validate
* @returns True if the object is a valid TImportableEd25519Key
*/
isValid(key) {
return (typeof key === 'object' &&
key !== null &&
typeof key.publicKeyHex === 'string' &&
isHex(key.publicKeyHex) &&
typeof key.privateKeyHex === 'string' &&
isHex(key.privateKeyHex) &&
typeof key.kid === 'string' &&
key.type === 'Ed25519');
},
};
/** Multicodec header for Ed25519 public keys */
const MULTICODEC_ED25519_HEADER = new Uint8Array([0xed, 0x01]);
/**
* Compares two arrays of key-value pairs for equality.
*
* @param kv1 - First array of key-value pairs
* @param kv2 - Second array of key-value pairs
* @returns True if both arrays contain identical key-value pairs in the same order
*/
export function isEqualKeyValuePair(kv1, kv2) {
return kv1.every((item, index) => item.key === kv2[index].key && item.value === kv2[index].value);
}
/**
* Extended English mnemonic class with additional validation patterns.
* Provides regex pattern matching for mnemonic phrase validation.
*/
export class EnglishMnemonic extends _ {
/** Regular expression pattern for validating English mnemonic phrases */
static _mnemonicMatcher = /^[a-z]+( [a-z]+)*$/;
}
/**
* Creates signing inputs from an importable Ed25519 key by matching it with verification methods.
* Supports multiple verification method types and key formats.
*
* @param key - The Ed25519 key to create signing inputs from
* @param verificationMethod - Array of verification methods to match against
* @returns Signing inputs containing verification method ID and private key
* @throws Error if key validation fails or no matching verification method is found
*/
export function createSignInputsFromImportableEd25519Key(key, verificationMethod) {
if (!TImportableEd25519Key.isValid(key))
throw new Error(`Key validation failed. Expected ${Object.values(TImportableEd25519Key).join(', ')}`);
const publicKey = fromString(key.publicKeyHex, 'hex');
for (const method of verificationMethod) {
switch (method.type) {
case VerificationMethods.Ed255192020:
const publicKeyMultibase = toMultibaseRaw(publicKey);
if (method.publicKeyMultibase === publicKeyMultibase) {
return {
verificationMethodId: method.id,
privateKeyHex: key.privateKeyHex,
};
}
case VerificationMethods.Ed255192018:
const publicKeyBase58 = bases['base58btc'].encode(publicKey).slice(1);
if (method.publicKeyBase58 === publicKeyBase58) {
return {
verificationMethodId: method.id,
privateKeyHex: key.privateKeyHex,
};
}
case VerificationMethods.JWK:
const publicKeyJwk = {
crv: 'Ed25519',
kty: 'OKP',
x: toString(publicKey, 'base64url'),
};
if (JSON.stringify(method.publicKeyJwk) === JSON.stringify(publicKeyJwk)) {
return {
verificationMethodId: method.id,
privateKeyHex: key.privateKeyHex,
};
}
}
throw new Error(`Unsupported verification method type: ${method.type}. Expected one of: ${Object.values(VerificationMethods).join(', ')}`);
}
throw new Error(`No verification method type provided. Expected one of: ${Object.values(VerificationMethods).join(', ')}`);
}
/**
* Creates a raw Ed25519 key pair using the StableLib library.
*
* @param seed - Optional seed string for deterministic key generation
* @returns Raw KeyPair object with publicKey and secretKey as Uint8Arrays
*/
export function createKeyPairRaw(seed) {
return seed ? generateKeyPairFromSeed(fromString(seed)) : generateKeyPair();
}
/**
* Creates an Ed25519 key pair with Base64-encoded keys.
*
* @param seed - Optional seed string for deterministic key generation
* @returns Key pair with Base64-encoded public and private keys
*/
export function createKeyPairBase64(seed) {
const keyPair = seed ? generateKeyPairFromSeed(fromString(seed)) : generateKeyPair();
return {
publicKey: toString(keyPair.publicKey, 'base64'),
privateKey: toString(keyPair.secretKey, 'base64'),
};
}
/**
* Creates an Ed25519 key pair with hexadecimal-encoded keys.
*
* @param seed - Optional seed string for deterministic key generation
* @returns Key pair with hexadecimal-encoded public and private keys
*/
export function createKeyPairHex(seed) {
const keyPair = createKeyPairRaw(seed);
return {
publicKey: toString(keyPair.publicKey, 'hex'),
privateKey: toString(keyPair.secretKey, 'hex'),
};
}
/**
* Creates verification keys structure with DID URLs and key identifiers.
* Supports multiple algorithm types and network configurations.
*
* @param publicKey - Public key in base64 or hex format
* @param algo - Algorithm for method-specific ID generation
* @param keyFragment - Key fragment for the verification key identifier
* @param network - Cheqd network (defaults to Testnet)
* @param methodSpecificId - Optional pre-computed method-specific ID
* @param didUrl - Optional pre-computed DID URL
* @returns Verification keys structure with all identifiers
* @throws Error if public key format is invalid
*/
export function createVerificationKeys(publicKey, algo, keyFragment, network = CheqdNetwork.Testnet, methodSpecificId, didUrl) {
if (isHex(publicKey)) {
publicKey = toString(fromString(publicKey, 'hex'), 'base64');
}
else if (!isBase64(publicKey)) {
throw new Error('publicKey validation failed. PublicKey should be in base64 or hex format');
}
switch (algo) {
case MethodSpecificIdAlgo.Base58:
methodSpecificId ||= bases['base58btc'].encode(base64ToBytes(publicKey));
didUrl ||= `did:cheqd:${network}:${bases['base58btc']
.encode(sha256(base64ToBytes(publicKey)).slice(0, 16))
.slice(1)}`;
return {
methodSpecificId,
didUrl,
keyId: `${didUrl}#${keyFragment}`,
publicKey,
};
case MethodSpecificIdAlgo.Uuid:
methodSpecificId ||= v4();
didUrl ||= `did:cheqd:${network}:${methodSpecificId}`;
return {
methodSpecificId,
didUrl,
keyId: `${didUrl}#${keyFragment}`,
publicKey,
};
}
}
/**
* Creates DID verification methods from verification method types and keys.
* Supports Ed25519 keys in multiple formats (multibase, base58, JWK).
*
* @param verificationMethodTypes - Array of verification method types to create
* @param verificationKeys - Array of verification keys corresponding to each type
* @returns Array of formatted verification methods for DID documents
*/
export function createDidVerificationMethod(verificationMethodTypes, verificationKeys) {
return (verificationMethodTypes.map((type, _) => {
switch (type) {
case VerificationMethods.Ed255192020:
return {
id: verificationKeys[_].keyId,
type,
controller: verificationKeys[_].didUrl,
publicKeyMultibase: toMultibaseRaw(base64ToBytes(verificationKeys[_].publicKey)),
};
case VerificationMethods.Ed255192018:
return {
id: verificationKeys[_].keyId,
type,
controller: verificationKeys[_].didUrl,
publicKeyBase58: bases['base58btc']
.encode(base64ToBytes(verificationKeys[_].publicKey))
.slice(1),
};
case VerificationMethods.JWK:
return {
id: verificationKeys[_].keyId,
type,
controller: verificationKeys[_].didUrl,
publicKeyJwk: {
crv: 'Ed25519',
kty: 'OKP',
x: toString(fromString(verificationKeys[_].publicKey, 'base64pad'), 'base64url'),
},
};
}
}) ?? []);
}
/**
* Creates a complete DID document payload with verification methods and controllers.
*
* @param verificationMethods - Array of verification methods for the DID
* @param verificationKeys - Array of verification keys for authentication
* @param controller - Optional array of controller DIDs (defaults to self-controlled)
* @returns Complete DID document with all required fields
* @throws Error if verification methods or keys are missing
*/
export function createDidPayload(verificationMethods, verificationKeys, controller = []) {
if (!verificationMethods || verificationMethods.length === 0)
throw new Error('No verification methods provided');
if (!verificationKeys || verificationKeys.length === 0)
throw new Error('No verification keys provided');
const did = verificationKeys[0].didUrl;
return {
id: did,
controller: controller.length ? controller : Array.from(new Set(verificationKeys.map((key) => key.didUrl))),
verificationMethod: verificationMethods,
authentication: verificationKeys.map((key) => key.keyId),
assertionMethod: verificationKeys.map((key) => key.keyId),
};
}
/**
* Validates a DID document against the Cheqd specification and converts to protobuf format.
* Ensures all required fields are present and verification methods are supported.
*
* @param didDocument - DID document to validate
* @returns Validation result with protobuf conversion or error details
*/
export function 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 VerificationMethods.Ed255192020:
if (!vm.publicKeyMultibase)
throw new Error('publicKeyMultibase is required');
return ProtoVerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: VerificationMethods.Ed255192020,
verificationMaterial: vm.publicKeyMultibase,
});
case VerificationMethods.JWK:
if (!vm.publicKeyJwk)
throw new Error('publicKeyJwk is required');
return ProtoVerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: VerificationMethods.JWK,
verificationMaterial: JSON.stringify(vm.publicKeyJwk),
});
case VerificationMethods.Ed255192018:
if (!vm.publicKeyBase58)
throw new Error('publicKeyBase58 is required');
return ProtoVerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: VerificationMethods.Ed255192018,
verificationMaterial: vm.publicKeyBase58,
});
default:
throw new Error(`Unsupported verificationMethod type: ${vm?.type}`);
}
});
const protoService = normalizeService(didDocument);
return { valid: true, protobufVerificationMethod: protoVerificationMethod, protobufService: protoService };
}
/**
* Creates a Cosmos wallet from a seed phrase or private key.
* Supports both HD wallets from mnemonics and direct key wallets.
*
* @param cosmosPayerSeed - Mnemonic phrase or private key hex string
* @returns Promise resolving to either HD wallet or direct key wallet
*/
export function createCosmosPayerWallet(cosmosPayerSeed) {
return EnglishMnemonic._mnemonicMatcher.test(cosmosPayerSeed)
? DirectSecp256k1HdWallet.fromMnemonic(cosmosPayerSeed, { prefix: 'cheqd' })
: DirectSecp256k1Wallet.fromKey(fromString(cosmosPayerSeed.replace(/^0x/, ''), 'hex'), 'cheqd');
}
/**
* Converts a raw Ed25519 public key to multibase format with proper multicodec header.
*
* @param key - Raw Ed25519 public key as Uint8Array
* @returns Multibase-encoded string with Ed25519 multicodec prefix
*/
export function toMultibaseRaw(key) {
const multibase = new Uint8Array(MULTICODEC_ED25519_HEADER.length + key.length);
multibase.set(MULTICODEC_ED25519_HEADER);
multibase.set(key, MULTICODEC_ED25519_HEADER.length);
return bases['base58btc'].encode(multibase);
}
/**
* Creates a MsgCreateDidDoc payload ready for signing.
* Validates the DID document and converts it to protobuf format.
*
* @param didPayload - DID document to create message payload from
* @param versionId - Version identifier for the DID document
* @returns Encoded message payload bytes ready for signing
*/
export async function createMsgCreateDidDocPayloadToSign(didPayload, versionId) {
const { protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload);
return MsgCreateDidDocPayload.encode(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,
})).finish();
}
/** Alias for createMsgCreateDidDocPayloadToSign - used for DID document updates */
export const createMsgUpdateDidDocPayloadToSign = createMsgCreateDidDocPayloadToSign;
/**
* Creates a MsgDeactivateDidDoc payload ready for signing.
*
* @param didPayload - DID document containing the ID to deactivate
* @param versionId - Optional version identifier for the DID document
* @returns Encoded message payload bytes ready for signing
*/
export function createMsgDeactivateDidDocPayloadToSign(didPayload, versionId) {
return MsgDeactivateDidDocPayload.encode(MsgDeactivateDidDocPayload.fromPartial({
id: didPayload.id,
versionId,
})).finish();
}
/**
* Creates a resource payload ready for signing.
*
* @param payload - Resource payload to encode
* @returns Encoded resource payload bytes ready for signing
*/
export function createMsgResourcePayloadToSign(payload) {
return MsgCreateResourcePayload.encode(MsgCreateResourcePayload.fromPartial(payload)).finish();
}
/**
* Converts a public key to a Cosmos account address.
*
* @param publicKeyHex - Public key in hexadecimal format
* @returns Bech32-encoded Cosmos account address with 'cheqd' prefix
*/
export function getCosmosAccount(publicKeyHex) {
const { publicKeyConvert } = pkg;
return toBech32('cheqd', rawSecp256k1PubkeyToRawAddress(publicKeyConvert(fromString(publicKeyHex, 'hex'), true)));
}
/**
* Checks the balance of all coins for a given address on the blockchain.
*
* @param address - Bech32-encoded account address to check balance for
* @param rpcAddress - RPC endpoint URL of the blockchain node
* @returns Promise resolving to array of coin balances
*/
export async function checkBalance(address, rpcAddress) {
const client = await StargateClient.connect(rpcAddress);
return await client.getAllBalances(address);
}
/**
* Checks if a given input is valid JSON.
*
* @param input - Input to validate as JSON
* @returns True if the input is a valid JSON string
*/
export function isJSON(input) {
if (typeof input !== 'string')
return false;
try {
JSON.parse(input);
return true;
}
catch (e) {
return false;
}
}
/** Default configuration options for exponential backoff retry logic */
export const DefaultBackoffOptions = {
jitter: 'full',
timeMultiple: 1,
delayFirstAttempt: false,
maxDelay: 100,
startingDelay: 100,
numOfAttempts: 3,
};
export async function retry(fn, options) {
// set default options
if (!options) {
options = DefaultBackoffOptions;
}
else {
// overwrite defaults with user supplied options
options = { ...DefaultBackoffOptions, ...options };
}
let result;
try {
result = await backOff(fn, options);
}
catch (e) {
console.error(e);
}
return result;
}
function isBase64(str) {
// Quick pattern check to filter obvious non-base64 strings
const base64Pattern = /^[A-Za-z0-9+/]*={0,3}$/;
if (!base64Pattern.test(str)) {
return false;
}
try {
return toString(fromString(str, 'base64'), 'base64') === str;
}
catch (e) {
return false;
}
}
function isHex(str) {
// Quick pattern check to filter obvious non-hex strings
const hexPattern = /^[0-9a-fA-F]*$/;
if (!hexPattern.test(str)) {
return false;
}
try {
return toString(fromString(str, 'hex'), 'hex') === str;
}
catch {
return false;
}
}
/**
* Normalizes the authentication property of a DID document to an array of strings.
* Extracts authentication method identifiers from mixed string/object format.
*
* @param didDocument - DID document to normalize authentication for
* @returns Array of authentication method identifiers
* @throws Error if authentication section is missing
*/
export function normalizeAuthentication(didDocument) {
if (!didDocument.authentication)
throw new Error('Invalid DID Document: Authentication section is required in DID Document');
const authArray = Array.isArray(didDocument.authentication)
? didDocument.authentication
: [didDocument.authentication];
return authArray.map((a) => (typeof a === 'string' ? a : a.id));
}
/**
* Normalizes the controller property of a DID document to an array of strings.
* Defaults to self-controlled if no controller is specified.
*
* @param didDocument - DID document to normalize controller for
* @returns Array of controller DID identifiers
*/
export function normalizeController(didDocument) {
if (!didDocument.controller)
return [didDocument.id];
return Array.isArray(didDocument.controller) ? didDocument.controller : [didDocument.controller];
}
/**
* Normalizes DID document services to protobuf format.
* Converts service endpoints to arrays and includes optional properties.
*
* @param didDocument - DID document containing services to normalize
* @returns Array of protobuf-formatted services or undefined if no services
*/
export function normalizeService(didDocument) {
return didDocument.service?.map((s) => {
return ProtoService.fromPartial({
id: s?.id,
serviceType: s?.type,
serviceEndpoint: s ? (Array.isArray(s.serviceEndpoint) ? s.serviceEndpoint : [s.serviceEndpoint]) : [],
...(s?.recipientKeys && { recipientKeys: s.recipientKeys }),
...(s?.routingKeys && { routingKeys: s.routingKeys }),
...(s?.accept && { accept: s.accept }),
...(s?.priority !== undefined && { priority: s.priority }),
});
});
}
/**
* Converts protobuf services back to standard DID document service format.
* Handles special context requirements for LinkedDomains services.
*
* @param didDocument - Protobuf DID document containing services to denormalize
* @returns Array of standard DID document services
*/
export function denormalizeService(didDocument) {
return didDocument.service.map((s) => {
if (s.serviceType === ServiceType.LinkedDomains)
didDocument.context = [...didDocument.context, contexts.LinkedDomainsContext];
return {
id: s.id,
type: s.serviceType,
serviceEndpoint: Array.isArray(s.serviceEndpoint)
? s.serviceEndpoint.length === 1
? s.serviceEndpoint[0]
: s.serviceEndpoint
: s?.serviceEndpoint,
...(s.recipientKeys && { recipientKeys: s.recipientKeys }),
...(s.routingKeys && { routingKeys: s.routingKeys }),
...(s.accept && { accept: s.accept }),
...(s.priority !== undefined && { priority: s.priority }),
};
});
}
//# sourceMappingURL=utils.js.map