From e4ded4b98dd6ec59f84bae05f71525ae85b2c243 Mon Sep 17 00:00:00 2001 From: David McFadzean Date: Wed, 20 Mar 2024 09:52:40 -0400 Subject: [PATCH] Renamed txn to operation --- gatekeeper-sdk.js | 16 ++--- gatekeeper.js | 146 ++++++++++++++++++++++----------------------- gatekeeper.test.js | 82 ++++++++++++------------- keychain-cli.js | 8 +-- keymaster.js | 30 +++++----- keymaster.test.js | 8 +-- server.js | 20 +++---- 7 files changed, 155 insertions(+), 155 deletions(-) diff --git a/gatekeeper-sdk.js b/gatekeeper-sdk.js index 7cf6cf27..5af52c37 100644 --- a/gatekeeper-sdk.js +++ b/gatekeeper-sdk.js @@ -27,9 +27,9 @@ export async function getVersion() { } } -export async function createDID(txn) { +export async function createDID(operation) { try { - const response = await axios.post(`${URL}/did/`, txn); + const response = await axios.post(`${URL}/did/`, operation); return response.data; } catch (error) { @@ -53,9 +53,9 @@ export async function resolveDID(did, asof = null) { } } -export async function updateDID(txn) { +export async function updateDID(operation) { try { - const response = await axios.post(`${URL}/did/${txn.did}`, txn); + const response = await axios.post(`${URL}/did/${operation.did}`, operation); return response.data; } catch (error) { @@ -63,9 +63,9 @@ export async function updateDID(txn) { } } -export async function deleteDID(txn) { +export async function deleteDID(operation) { try { - const response = await axios.delete(`${URL}/did/${txn.did}`, { data: txn }); + const response = await axios.delete(`${URL}/did/${operation.did}`, { data: operation }); return response.data; } catch (error) { @@ -83,9 +83,9 @@ export async function exportDID(did) { } } -export async function importDID(txns) { +export async function importDID(ops) { try { - const response = await axios.post(`${URL}/import/`, txns); + const response = await axios.post(`${URL}/import/`, ops); return response.data; } catch (error) { diff --git a/gatekeeper.js b/gatekeeper.js index b3203592..2b6912b3 100644 --- a/gatekeeper.js +++ b/gatekeeper.js @@ -74,14 +74,14 @@ export async function stop() { helia.stop(); } -function submitTxn(did, registry, txn, time, ordinal = 0) { +function submitTxn(did, registry, operation, time, ordinal = 0) { const db = loadDb(); const update = { time: time, ordinal: ordinal, did: did, - txn: txn, + operation: operation, }; if (!db.hasOwnProperty(registry)) { @@ -121,91 +121,91 @@ export async function anchorSeed(seed) { return did; } -export async function generateDID(txn) { - const did = await anchorSeed(txn); - const txns = await exportDID(did); +export async function generateDID(operation) { + const did = await anchorSeed(operation); + const ops = await exportDID(did); - if (txns.length === 0) { - submitTxn(did, txn.mdip.registry, txn, txn.created); + if (ops.length === 0) { + submitTxn(did, operation.mdip.registry, operation, operation.created); } return did; } -async function createAgent(txn) { - if (!txn.signature) { - throw "Invalid txn"; +async function createAgent(operation) { + if (!operation.signature) { + throw "Invalid operation"; } - if (!txn.publicJwk) { - throw "Invalid txn"; + if (!operation.publicJwk) { + throw "Invalid operation"; } - const txnCopy = JSON.parse(JSON.stringify(txn)); - delete txnCopy.signature; + const operationCopy = JSON.parse(JSON.stringify(operation)); + delete operationCopy.signature; - const msgHash = cipher.hashJSON(txnCopy); - const isValid = cipher.verifySig(msgHash, txn.signature.value, txn.publicJwk); + const msgHash = cipher.hashJSON(operationCopy); + const isValid = cipher.verifySig(msgHash, operation.signature.value, operation.publicJwk); if (!isValid) { - throw "Invalid txn"; + throw "Invalid operation"; } - return generateDID(txn); + return generateDID(operation); } -async function createAsset(txn) { - if (txn.controller !== txn.signature.signer) { - throw "Invalid txn"; +async function createAsset(operation) { + if (operation.controller !== operation.signature.signer) { + throw "Invalid operation"; } - const doc = await resolveDID(txn.signature.signer, txn.signature.signed); - const txnCopy = JSON.parse(JSON.stringify(txn)); - delete txnCopy.signature; - const msgHash = cipher.hashJSON(txnCopy); + const doc = await resolveDID(operation.signature.signer, operation.signature.signed); + const operationCopy = JSON.parse(JSON.stringify(operation)); + delete operationCopy.signature; + const msgHash = cipher.hashJSON(operationCopy); // TBD select the right key here, not just the first one const publicJwk = doc.didDocument.verificationMethod[0].publicKeyJwk; - const isValid = cipher.verifySig(msgHash, txn.signature.value, publicJwk); + const isValid = cipher.verifySig(msgHash, operation.signature.value, publicJwk); if (!isValid) { - throw "Invalid txn"; + throw "Invalid operation"; } - return generateDID(txn); + return generateDID(operation); } -export async function createDID(txn) { - if (txn?.op !== "create") { - throw "Invalid txn"; +export async function createDID(operation) { + if (operation?.type !== "create") { + throw "Invalid operation"; } - if (!txn.created) { + if (!operation.created) { // TBD ensure valid timestamp format - throw "Invalid txn"; + throw "Invalid operation"; } - if (!txn.mdip) { - throw "Invalid txn"; + if (!operation.mdip) { + throw "Invalid operation"; } - if (!validVersions.includes(txn.mdip.version)) { + if (!validVersions.includes(operation.mdip.version)) { throw `Valid versions include: ${validVersions}`; } - if (!validTypes.includes(txn.mdip.type)) { + if (!validTypes.includes(operation.mdip.type)) { throw `Valid types include: ${validTypes}`; } - if (!validRegistries.includes(txn.mdip.registry)) { + if (!validRegistries.includes(operation.mdip.registry)) { throw `Valid registries include: ${validRegistries}`; } - if (txn.mdip.type === 'agent') { - return createAgent(txn); + if (operation.mdip.type === 'agent') { + return createAgent(operation); } - if (txn.mdip.type === 'asset') { - return createAsset(txn); + if (operation.mdip.type === 'asset') { + return createAsset(operation); } throw "Unknown type"; @@ -299,19 +299,19 @@ async function generateDoc(did, asofTime) { return {}; // TBD unknown type error } -async function verifyUpdate(txn, doc) { +async function verifyUpdate(operation, doc) { if (!doc?.didDocument) { return false; } if (doc.didDocument.controller) { - const controllerDoc = await resolveDID(doc.didDocument.controller, txn.signature.signed); - return verifyUpdate(txn, controllerDoc); + const controllerDoc = await resolveDID(doc.didDocument.controller, operation.signature.signed); + return verifyUpdate(operation, controllerDoc); } if (doc.didDocument.verificationMethod) { - const jsonCopy = JSON.parse(JSON.stringify(txn)); + const jsonCopy = JSON.parse(JSON.stringify(operation)); const signature = jsonCopy.signature; delete jsonCopy.signature; @@ -357,19 +357,19 @@ export async function resolveDID(did, asOfTime = null, verify = false) { const updates = fetchUpdates(mdip.registry, did); - for (const { time, txn } of updates) { + for (const { time, operation } of updates) { if (asOfTime && new Date(time) > new Date(asOfTime)) { break; } - if (txn.op === 'create') { + if (operation.type === 'create') { // Proof-of-existence in the DID's registry continue; } const hash = cipher.hashJSON(doc); - if (hash !== txn.prev) { + if (hash !== operation.prev) { // hash mismatch // if (verify) { // throw "Invalid hash"; @@ -378,7 +378,7 @@ export async function resolveDID(did, asOfTime = null, verify = false) { // continue; } - const valid = await verifyUpdate(txn, doc); + const valid = await verifyUpdate(operation, doc); if (!valid) { if (verify) { @@ -388,17 +388,17 @@ export async function resolveDID(did, asOfTime = null, verify = false) { continue; } - if (txn.op === 'update') { + if (operation.type === 'update') { // Maintain mdip metadata across versions mdip = doc.didDocumentMetadata.mdip; - // TBD if registry change in txn.doc.didDocumentMetadata.mdip, - // fetch updates from new registry and search for same txn - doc = txn.doc; + // TBD if registry change in operation.doc.didDocumentMetadata.mdip, + // fetch updates from new registry and search for same operation + doc = operation.doc; doc.didDocumentMetadata.updated = time; doc.didDocumentMetadata.mdip = mdip; } - else if (txn.op === 'delete') { + else if (operation.type === 'delete') { doc.didDocument = {}; doc.didDocumentMetadata.deactivated = true; doc.didDocumentMetadata.data = null; // in case of asset @@ -409,17 +409,17 @@ export async function resolveDID(did, asOfTime = null, verify = false) { throw "Invalid operation"; } - console.error(`unknown op ${txn.op}`); + console.error(`unknown type ${operation.type}`); } } return doc; } -export async function updateDID(txn) { +export async function updateDID(operation) { try { - const doc = await resolveDID(txn.did); - const updateValid = await verifyUpdate(txn, doc); + const doc = await resolveDID(operation.did); + const updateValid = await verifyUpdate(operation, doc); if (!updateValid) { return false; @@ -428,7 +428,7 @@ export async function updateDID(txn) { const registry = doc.didDocumentMetadata.mdip.registry; // TBD figure out time for blockchain registries - submitTxn(txn.did, registry, txn, txn.signature.signed); + submitTxn(operation.did, registry, operation, operation.signature.signed); return true; } catch (error) { @@ -437,8 +437,8 @@ export async function updateDID(txn) { } } -export async function deleteDID(txn) { - return updateDID(txn); +export async function deleteDID(operation) { + return updateDID(operation); } export async function exportDID(did) { @@ -452,39 +452,39 @@ export async function exportDID(did) { return fetchUpdates(registry, did); } -export async function importDID(txns) { +export async function importDID(ops) { - if (!txns || !Array.isArray(txns) || txns.length < 1) { + if (!ops || !Array.isArray(ops) || ops.length < 1) { throw "Invalid import"; } - const create = txns[0]; + const create = ops[0]; const did = create.did; const current = await exportDID(did); if (current.length === 0) { - const check = await createDID(create.txn); + const check = await createDID(create.operation); if (did !== check) { throw "Invalid import"; } } else { - if (create.txn.signature.value !== current[0].txn.signature.value) { + if (create.operation.signature.value !== current[0].operation.signature.value) { throw "Invalid import"; } } - for (let i = 1; i < txns.length; i++) { + for (let i = 1; i < ops.length; i++) { if (i < current.length) { - // Verify previous update txns - if (txns[i].txn.signature.value !== current[i].txn.signature.value) { + // Verify previous update ops + if (ops[i].operation.signature.value !== current[i].operation.signature.value) { throw "Invalid import"; } } else { // Add new updates - const ok = await updateDID(txns[i].txn); + const ok = await updateDID(ops[i].operation); if (!ok) { throw "Invalid import"; @@ -503,9 +503,9 @@ export async function mergeBatch(batch) { let updated = 0; let failed = 0; - for (const txns of batch) { + for (const ops of batch) { try { - const diff = await importDID(txns); + const diff = await importDID(ops); if (diff > 0) { updated += 1; diff --git a/gatekeeper.test.js b/gatekeeper.test.js index 71fd8784..4ab878f9 100644 --- a/gatekeeper.test.js +++ b/gatekeeper.test.js @@ -17,11 +17,11 @@ describe('generateDid', () => { mockFs.restore(); }); - it('should create DID from txn', async () => { + it('should create DID from operation', async () => { mockFs({}); const mockTxn = { - op: "create", + type: "create", created: new Date().toISOString(), mdip: { registry: "mockRegistry" @@ -33,11 +33,11 @@ describe('generateDid', () => { expect(did.startsWith('did:mdip:')); }); - it('should create same DID from same txn with date included', async () => { + it('should create same DID from same operation with date included', async () => { mockFs({}); const mockTxn = { - op: "create", + type: "create", created: new Date().toISOString(), mdip: { registry: "mockRegistry" @@ -51,8 +51,8 @@ describe('generateDid', () => { }); async function createAgentTxn(keypair, version = 1, registry = 'hyperswarm') { - const txn = { - op: "create", + const operation = { + type: "create", created: new Date().toISOString(), mdip: { version: version, @@ -62,11 +62,11 @@ async function createAgentTxn(keypair, version = 1, registry = 'hyperswarm') { publicJwk: keypair.publicJwk, }; - const msgHash = cipher.hashJSON(txn); + const msgHash = cipher.hashJSON(operation); const signature = await cipher.signHash(msgHash, keypair.privateJwk); return { - ...txn, + ...operation, signature: { signed: new Date().toISOString(), hash: msgHash, @@ -79,18 +79,18 @@ async function createUpdateTxn(keypair, did, doc) { const current = await gatekeeper.resolveDID(did); const prev = cipher.hashJSON(current); - const txn = { - op: "update", + const operation = { + type: "update", did: did, doc: doc, prev: prev, }; - const msgHash = cipher.hashJSON(txn); + const msgHash = cipher.hashJSON(operation); const signature = await cipher.signHash(msgHash, keypair.privateJwk); const signed = { - ...txn, + ...operation, signature: { signer: did, created: new Date().toISOString(), @@ -104,7 +104,7 @@ async function createUpdateTxn(keypair, did, doc) { async function createAssetTxn(agent, keypair) { const dataAnchor = { - op: "create", + type: "create", created: new Date().toISOString(), mdip: { version: 1, @@ -135,7 +135,7 @@ describe('createDID', () => { mockFs.restore(); }); - it('should create DID from agent txn', async () => { + it('should create DID from agent operation', async () => { mockFs({}); const keypair = cipher.generateRandomJwk(); @@ -224,7 +224,7 @@ describe('createDID', () => { } }); - it('should create DID from asset txn', async () => { + it('should create DID from asset operation', async () => { mockFs({}); const keypair = cipher.generateRandomJwk(); @@ -252,18 +252,18 @@ describe('exportDID', () => { const agentTxn = await createAgentTxn(keypair); const did = await gatekeeper.createDID(agentTxn); - const txns = await gatekeeper.exportDID(did); + const ops = await gatekeeper.exportDID(did); - expect(txns.length).toBe(1); - expect(txns[0].did).toStrictEqual(did); - expect(txns[0].txn).toStrictEqual(agentTxn); + expect(ops.length).toBe(1); + expect(ops[0].did).toStrictEqual(did); + expect(ops[0].operation).toStrictEqual(agentTxn); }); it('should return empty array on an invalid DID', async () => { mockFs({}); - const txns = await gatekeeper.exportDID('mockDID'); - expect(txns).toStrictEqual([]); + const ops = await gatekeeper.exportDID('mockDID'); + expect(ops).toStrictEqual([]); }); }); @@ -279,9 +279,9 @@ describe('importDID', () => { const keypair = cipher.generateRandomJwk(); const agentTxn = await createAgentTxn(keypair); const did = await gatekeeper.createDID(agentTxn); - const txns = await gatekeeper.exportDID(did); + const ops = await gatekeeper.exportDID(did); - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); expect(imported).toBe(0); }); @@ -294,14 +294,14 @@ describe('importDID', () => { const agentDID = await gatekeeper.createDID(agentTxn); const assetTxn = await createAssetTxn(agentDID, keypair); const assetDID = await gatekeeper.createDID(assetTxn); - const txns = await gatekeeper.exportDID(assetDID); + const ops = await gatekeeper.exportDID(assetDID); - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); expect(imported).toBe(0); }); - it('should report 0 txns reported when DID exists', async () => { + it('should report 0 ops reported when DID exists', async () => { mockFs({}); const keypair = cipher.generateRandomJwk(); @@ -310,14 +310,14 @@ describe('importDID', () => { const doc = await gatekeeper.resolveDID(did); const updateTxn = await createUpdateTxn(keypair, did, doc); const ok = await gatekeeper.updateDID(updateTxn); - const txns = await gatekeeper.exportDID(did); + const ops = await gatekeeper.exportDID(did); - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); expect(imported).toBe(0); }); - it('should report 2 txns imported when DID deleted first', async () => { + it('should report 2 ops imported when DID deleted first', async () => { mockFs({}); const keypair = cipher.generateRandomJwk(); @@ -326,15 +326,15 @@ describe('importDID', () => { const doc = await gatekeeper.resolveDID(did); const updateTxn = await createUpdateTxn(keypair, did, doc); const ok = await gatekeeper.updateDID(updateTxn); - const txns = await gatekeeper.exportDID(did); + const ops = await gatekeeper.exportDID(did); fs.rmSync(gatekeeper.dbName); - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); expect(imported).toBe(2); }); - it('should report N+1 txns imported for N updates', async () => { + it('should report N+1 ops imported for N updates', async () => { mockFs({}); const keypair = cipher.generateRandomJwk(); @@ -349,12 +349,12 @@ describe('importDID', () => { const ok = await gatekeeper.updateDID(updateTxn); } - const txns = await gatekeeper.exportDID(did); + const ops = await gatekeeper.exportDID(did); fs.rmSync(gatekeeper.dbName); - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); - expect(imported).toBe(N+1); + expect(imported).toBe(N + 1); }); it('should resolve an imported DID', async () => { @@ -363,11 +363,11 @@ describe('importDID', () => { const keypair = cipher.generateRandomJwk(); const agentTxn = await createAgentTxn(keypair); const did = await gatekeeper.createDID(agentTxn); - const txns = await gatekeeper.exportDID(did); + const ops = await gatekeeper.exportDID(did); fs.rmSync(gatekeeper.dbName); - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); const doc = await gatekeeper.resolveDID(did); expect(doc.didDocument.id).toBe(did); @@ -381,12 +381,12 @@ describe('importDID', () => { const did1 = await gatekeeper.createDID(agentTxn1); const agentTxn2 = await createAgentTxn(keypair); const did2 = await gatekeeper.createDID(agentTxn2); - const txns = await gatekeeper.exportDID(did1); + const ops = await gatekeeper.exportDID(did1); - txns[0].did = did2; + ops[0].did = did2; try { - const imported = await gatekeeper.importDID(txns); + const imported = await gatekeeper.importDID(ops); throw 'Expected to throw an exception'; } catch (error) { expect(error).toBe('Invalid import'); @@ -433,7 +433,7 @@ describe('importDID', () => { const imported = await gatekeeper.importDID([1, 2, 3]); throw 'Expected to throw an exception'; } catch (error) { - expect(error).toBe('Invalid txn'); + expect(error).toBe('Invalid operation'); } }); }); diff --git a/keychain-cli.js b/keychain-cli.js index aee84070..c550144a 100644 --- a/keychain-cli.js +++ b/keychain-cli.js @@ -503,8 +503,8 @@ program .description('Export DID to file') .action(async (did) => { try { - const txns = await keymaster.exportDID(did); - console.log(JSON.stringify(txns, null, 4)); + const ops = await keymaster.exportDID(did); + console.log(JSON.stringify(ops, null, 4)); } catch (error) { console.error(error); @@ -517,8 +517,8 @@ program .action(async (file) => { try { const contents = fs.readFileSync(file).toString(); - const txns = JSON.parse(contents); - const did = await keymaster.importDID(txns); + const ops = JSON.parse(contents); + const did = await keymaster.importDID(ops); console.log(did); } catch (error) { diff --git a/keymaster.js b/keymaster.js index f3c5e14b..346bf7be 100644 --- a/keymaster.js +++ b/keymaster.js @@ -229,14 +229,14 @@ async function updateDID(did, doc) { const current = await resolveDID(did); const prev = cipher.hashJSON(current); - const txn = { - op: "update", + const operation = { + type: "update", did: did, doc: doc, prev: prev, }; - const signed = await addSignature(txn); + const signed = await addSignature(operation); return gatekeeper.updateDID(signed); } @@ -244,13 +244,13 @@ async function revokeDID(did) { const current = await resolveDID(did); const prev = cipher.hashJSON(current); - const txn = { - op: "delete", + const operation = { + type: "delete", did: did, prev: prev, }; - const signed = await addSignature(txn); + const signed = await addSignature(operation); return gatekeeper.deleteDID(signed); } @@ -308,8 +308,8 @@ export async function createId(name, registry = defaultRegistry) { const didkey = hdkey.derive(path); const keypair = cipher.generateJwk(didkey.privateKey); - const txn = { - op: "create", + const operation = { + type: "create", created: new Date().toISOString(), mdip: { version: 1, @@ -319,10 +319,10 @@ export async function createId(name, registry = defaultRegistry) { publicJwk: keypair.publicJwk, }; - const msgHash = cipher.hashJSON(txn); + const msgHash = cipher.hashJSON(operation); const signature = await cipher.signHash(msgHash, keypair.privateJwk); const signed = { - ...txn, + ...operation, signature: { signed: new Date().toISOString(), hash: msgHash, @@ -518,8 +518,8 @@ export async function createData(data, registry = defaultRegistry) { const id = getCurrentId(); - const txn = { - op: "create", + const operation = { + type: "create", created: new Date().toISOString(), mdip: { version: 1, @@ -530,7 +530,7 @@ export async function createData(data, registry = defaultRegistry) { data: data, }; - const signed = await addSignature(txn); + const signed = await addSignature(operation); const did = await gatekeeper.createDID(signed); addToOwned(did); @@ -798,6 +798,6 @@ export async function exportDID(did) { return gatekeeper.exportDID(lookupDID(did)); } -export async function importDID(txns) { - return gatekeeper.importDID(txns); +export async function importDID(ops) { + return gatekeeper.importDID(ops); } diff --git a/keymaster.test.js b/keymaster.test.js index f9e4afb8..5a9760ed 100644 --- a/keymaster.test.js +++ b/keymaster.test.js @@ -420,11 +420,11 @@ describe('rotateKeys', () => { await keymaster.rotateKeys(); } - const txns = await keymaster.exportDID(alice); + const ops = await keymaster.exportDID(alice); fs.rmSync(gatekeeper.dbName); - const imported = await keymaster.importDID(txns); + const imported = await keymaster.importDID(ops); expect(imported).toBe(rotations + 1); }); @@ -1078,12 +1078,12 @@ describe('revokeCredential', () => { const ok = await keymaster.revokeCredential(did); const userTxns = await keymaster.exportDID(userDid); - const txns = await keymaster.exportDID(did); + const ops = await keymaster.exportDID(did); fs.rmSync(gatekeeper.dbName); await keymaster.importDID(userTxns); - const imported = await keymaster.importDID(txns); + const imported = await keymaster.importDID(ops); expect(imported).toBe(2); }); diff --git a/server.js b/server.js index a752a6ac..d41f7c9a 100644 --- a/server.js +++ b/server.js @@ -23,8 +23,8 @@ v1router.get('/version', async (req, res) => { v1router.post('/did', async (req, res) => { try { - const txn = req.body; - const did = await gatekeeper.createDID(txn); + const operation = req.body; + const did = await gatekeeper.createDID(operation); res.json(did); } catch (error) { console.error(error); @@ -44,8 +44,8 @@ v1router.get('/did/:did', async (req, res) => { v1router.post('/did/:did', async (req, res) => { try { - const txn = req.body; - const ok = await gatekeeper.updateDID(txn); + const operation = req.body; + const ok = await gatekeeper.updateDID(operation); res.json(ok); } catch (error) { console.error(error); @@ -55,8 +55,8 @@ v1router.post('/did/:did', async (req, res) => { v1router.delete('/did/:did', async (req, res) => { try { - const txn = req.body; - const ok = await gatekeeper.deleteDID(txn); + const operation = req.body; + const ok = await gatekeeper.deleteDID(operation); res.json(ok); } catch (error) { console.error(error); @@ -66,8 +66,8 @@ v1router.delete('/did/:did', async (req, res) => { v1router.get('/export/:did', async (req, res) => { try { - const txns = await gatekeeper.exportDID(req.params.did); - res.json(txns); + const ops = await gatekeeper.exportDID(req.params.did); + res.json(ops); } catch (error) { console.error(error); res.status(500).send(error.toString()); @@ -76,8 +76,8 @@ v1router.get('/export/:did', async (req, res) => { v1router.post('/import', async (req, res) => { try { - const txns = req.body; - const did = await gatekeeper.importDID(txns); + const ops = req.body; + const did = await gatekeeper.importDID(ops); res.json(did); } catch (error) { console.error(error);