did:cow, a proposal for an ID resolution method with most of the convenience of did:plc/did:web and the robustness of a public blockchain
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

update functions etc

+67 -55
+67 -55
web/static/index.html
··· 180 180 margin-top: 0.5rem; 181 181 } 182 182 183 - .radio-group { display: flex; gap: 1.5rem; padding: 0.25rem 0; } 184 - .radio-label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.95rem; cursor: pointer; } 185 183 </style> 186 184 </head> 187 185 <body> ··· 209 207 </div> 210 208 <button class="action" onclick="resolveDid()">Resolve</button> 211 209 </div> 212 - <div id="resolve-result" class="result" style="display:none"></div> 210 + <div id="resolve-status" class="result" style="display:none; margin-top: 1rem;"></div> 211 + <div id="resolve-edit" style="display:none; margin-top: 1rem;"> 212 + <div class="row" style="margin-bottom: 0.75rem;"> 213 + <div class="field"> 214 + <label>Controller</label> 215 + <input type="text" id="edit-controller" disabled> 216 + </div> 217 + <button class="action" id="edit-controller-btn" onclick="sendUpdate('controller')" disabled>Update</button> 218 + </div> 219 + <div class="row"> 220 + <div class="field"> 221 + <label>Wrapped DID</label> 222 + <input type="text" id="edit-wrapped" disabled> 223 + </div> 224 + <button class="action" id="edit-wrapped-btn" onclick="sendUpdate('wrapped')" disabled>Update</button> 225 + </div> 226 + <div id="edit-status" class="result" style="display:none; margin-top: 0.75rem;"></div> 227 + <label style="margin-top: 1.25rem; display: block;">DID Document</label> 228 + <div id="resolve-result" class="result" style="display:none; margin-top:0"></div> 229 + </div> 213 230 </div> 214 231 215 232 <!-- Create --> ··· 239 256 <div id="create-error" class="result error" style="display:none"></div> 240 257 </div> 241 258 242 - <!-- Manage (visible when connected) --> 243 - <div class="card" id="manage-card" style="display:none"> 244 - <h2>Manage</h2> 245 - <p class="hint" style="margin-bottom:0.75rem">Update a did:cow you control. The connected wallet must be the current controller.</p> 246 - <div class="field"> 247 - <label>DID</label> 248 - <input type="text" id="manage-did" placeholder="did:cow:8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be:plc:pyzlzqt6b2nyrha7smfry6rv"> 249 - </div> 250 - <div class="field"> 251 - <label>Update</label> 252 - <div class="radio-group"> 253 - <label class="radio-label"><input type="radio" name="update-type" value="controller" checked> Controller</label> 254 - <label class="radio-label"><input type="radio" name="update-type" value="wrapped"> Wrapped DID</label> 255 - </div> 256 - </div> 257 - <div class="field" id="new-controller-field"> 258 - <label>New controller address</label> 259 - <input type="text" id="new-controller" placeholder="8BC101ABF5BcF8b6209FaaAD4D761C1ED14999Be"> 260 - </div> 261 - <div class="field" id="new-wrapped-field" style="display:none"> 262 - <label>New wrapped DID</label> 263 - <input type="text" id="new-wrapped" placeholder="did:plc:pyzlzqt6b2nyrha7smfry6rv or plc:pyzlzqt6b2nyrha7smfry6rv"> 264 - </div> 265 - <button class="action" onclick="sendUpdate()">Send transaction</button> 266 - <div id="manage-status" class="result" style="display:none"></div> 267 - </div> 268 259 269 260 <script type="module"> 270 261 const API_BASE = "https://cow.watch"; ··· 305 296 controllerInput.value = toChecksumAddress(connectedAddress).slice(2); 306 297 } 307 298 document.getElementById("controller-hint").style.display = "inline"; 308 - document.getElementById("manage-card").style.display = "block"; 299 + // Enable edit fields if a DID has already been resolved 300 + if (document.getElementById("resolve-edit").style.display !== "none") { 301 + setEditEnabled(true); 302 + } 309 303 } catch (e) { 310 304 console.error(e); 311 305 } ··· 321 315 322 316 // ── Resolve ───────────────────────────────────────────────────────────────── 323 317 318 + let resolvedDid = null; // the DID last successfully resolved, for contract calls 319 + 320 + function setEditEnabled(enabled) { 321 + document.getElementById("edit-controller").disabled = !enabled; 322 + document.getElementById("edit-wrapped").disabled = !enabled; 323 + document.getElementById("edit-controller-btn").disabled = !enabled; 324 + document.getElementById("edit-wrapped-btn").disabled = !enabled; 325 + } 326 + 324 327 window.resolveDid = async () => { 325 328 const did = document.getElementById("resolve-did").value.trim(); 326 - const el = document.getElementById("resolve-result"); 327 - el.style.display = "block"; 328 - el.className = "result"; 329 - el.textContent = "Resolving…"; 329 + const statusEl = document.getElementById("resolve-status"); 330 + const resultEl = document.getElementById("resolve-result"); 331 + const editEl = document.getElementById("resolve-edit"); 332 + 333 + statusEl.style.display = "block"; 334 + statusEl.className = "result"; 335 + statusEl.textContent = "Resolving…"; 336 + editEl.style.display = "none"; 337 + resolvedDid = null; 330 338 331 339 try { 332 340 const resp = await fetch(`${API_BASE}/${encodeURIComponent(did)}`); 333 341 const body = await resp.json(); 334 342 if (!resp.ok) { 335 - el.className = "result error"; 336 - el.textContent = body.detail ?? JSON.stringify(body, null, 2); 343 + statusEl.className = "result error"; 344 + statusEl.textContent = body.detail ?? JSON.stringify(body, null, 2); 337 345 } else { 338 - el.className = "result success"; 339 - el.textContent = JSON.stringify(body, null, 2); 346 + statusEl.style.display = "none"; 347 + resultEl.className = "result"; 348 + resultEl.textContent = JSON.stringify(body, null, 2); 349 + 350 + const cowBlock = body["did:cow"]; 351 + if (cowBlock) { 352 + const controllerAddr = cowBlock.controller.split(":").pop(); 353 + document.getElementById("edit-controller").value = controllerAddr; 354 + document.getElementById("edit-wrapped").value = "did:" + cowBlock.wrappedDid.replace(/^did:/, ""); 355 + resolvedDid = did; 356 + editEl.style.display = "block"; 357 + resultEl.style.display = "block"; 358 + document.getElementById("edit-status").style.display = "none"; 359 + setEditEnabled(!!connectedAddress); 360 + } 340 361 } 341 362 } catch (e) { 342 - el.className = "result error"; 343 - el.textContent = `Network error: ${e.message}`; 363 + statusEl.className = "result error"; 364 + statusEl.textContent = `Network error: ${e.message}`; 344 365 } 345 366 }; 346 367 ··· 434 455 requestAnimationFrame(frame); 435 456 } 436 457 437 - // ── Manage ────────────────────────────────────────────────────────────────── 438 - 439 - document.querySelectorAll('input[name="update-type"]').forEach(radio => { 440 - radio.addEventListener("change", () => { 441 - document.getElementById("new-controller-field").style.display = radio.value === "controller" ? "" : "none"; 442 - document.getElementById("new-wrapped-field").style.display = radio.value === "wrapped" ? "" : "none"; 443 - }); 444 - }); 458 + // ── Edit ──────────────────────────────────────────────────────────────────── 445 459 446 460 function parseCowDid(did) { 447 461 if (!did.startsWith("did:cow:")) throw new Error("Not a did:cow DID"); ··· 454 468 }; 455 469 } 456 470 457 - window.sendUpdate = async () => { 458 - const statusEl = document.getElementById("manage-status"); 471 + window.sendUpdate = async (updateType) => { 472 + const statusEl = document.getElementById("edit-status"); 459 473 statusEl.style.display = "block"; 460 474 statusEl.className = "result"; 461 475 statusEl.textContent = "Preparing transaction…"; 462 476 463 477 try { 464 - const did = document.getElementById("manage-did").value.trim(); 465 - const { controller, wrapped } = parseCowDid(did); 466 - const updateType = document.querySelector('input[name="update-type"]:checked').value; 478 + const { controller, wrapped } = parseCowDid(resolvedDid); 467 479 468 480 const config = await loadConfig(); 469 481 const provider = new ethers.BrowserProvider(window.ethereum); ··· 482 494 483 495 let tx; 484 496 if (updateType === "controller") { 485 - const raw = document.getElementById("new-controller").value.trim().replace(/^0x/i, ""); 497 + const raw = document.getElementById("edit-controller").value.trim().replace(/^0x/i, ""); 486 498 if (!/^[0-9a-fA-F]{40}$/.test(raw)) throw new Error("Invalid controller address"); 487 499 tx = await contract.updateController(controller, wrapped, "0x" + raw); 488 500 } else { 489 - let newWrapped = document.getElementById("new-wrapped").value.trim(); 501 + let newWrapped = document.getElementById("edit-wrapped").value.trim(); 490 502 if (newWrapped.startsWith("did:")) newWrapped = newWrapped.slice(4); 491 503 if (!newWrapped.includes(":")) throw new Error("Invalid wrapped DID"); 492 504 tx = await contract.updateWrappedDID(controller, wrapped, newWrapped);