alpha
Login
or
Join now
goat.navy
/
did-cow
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
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
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
fix update error, add full resolution
author
Edmund Edgar
date
4 months ago
(Mar 10, 2026, 5:39 AM UTC)
commit
f8f78c6a
f8f78c6a2920be894bbd8e2a24d0f3de30ac57a6
parent
7b9c570a
7b9c570a0f94d50ef23c9082b2c72464b478d7af
+65
-9
2 changed files
Expand all
Collapse all
Unified
Split
cli
cow.py
requirements.txt
+64
-9
cli/cow.py
View file
Reviewed
···
7
7
from pathlib import Path
8
8
9
9
import click
10
10
+
import requests
10
11
from dotenv import load_dotenv
11
12
from web3 import Web3
12
13
···
109
110
110
111
111
112
# ---------------------------------------------------------------------------
113
113
+
# DID document resolution
114
114
+
# ---------------------------------------------------------------------------
115
115
+
116
116
+
def _resolve_did_doc(wrapped_did_no_prefix):
117
117
+
"""
118
118
+
Fetch the DID document for a wrapped DID (without leading 'did:').
119
119
+
Supports did:plc and did:web.
120
120
+
Returns the parsed JSON document, or raises ClickException on failure.
121
121
+
"""
122
122
+
full_did = f"did:{wrapped_did_no_prefix}"
123
123
+
124
124
+
if wrapped_did_no_prefix.startswith("plc:"):
125
125
+
url = f"https://plc.directory/{full_did}"
126
126
+
127
127
+
elif wrapped_did_no_prefix.startswith("web:"):
128
128
+
# did:web:example.com → https://example.com/.well-known/did.json
129
129
+
# did:web:example.com:path:to → https://example.com/path/to/did.json
130
130
+
host_and_path = wrapped_did_no_prefix[len("web:"):]
131
131
+
parts = host_and_path.split(":")
132
132
+
if len(parts) == 1:
133
133
+
url = f"https://{parts[0]}/.well-known/did.json"
134
134
+
else:
135
135
+
path = "/".join(parts[1:])
136
136
+
url = f"https://{parts[0]}/{path}/did.json"
137
137
+
138
138
+
else:
139
139
+
raise click.ClickException(
140
140
+
f"Resolution not supported for did:{wrapped_did_no_prefix} "
141
141
+
f"(only did:plc and did:web are supported)"
142
142
+
)
143
143
+
144
144
+
try:
145
145
+
resp = requests.get(url, timeout=10)
146
146
+
resp.raise_for_status()
147
147
+
except requests.exceptions.HTTPError as e:
148
148
+
raise click.ClickException(f"Failed to fetch DID document from {url}: {e}")
149
149
+
except requests.exceptions.RequestException as e:
150
150
+
raise click.ClickException(f"Network error fetching {url}: {e}")
151
151
+
152
152
+
return url, resp.json()
153
153
+
154
154
+
155
155
+
# ---------------------------------------------------------------------------
112
156
# Commands
113
157
# ---------------------------------------------------------------------------
114
158
···
124
168
125
169
@cli.command()
126
170
@click.argument("did")
127
127
-
def resolve(did):
128
128
-
"""Resolve a did:cow DID to its current state.
171
171
+
@click.option("--no-doc", is_flag=True, help="Skip fetching the wrapped DID document.")
172
172
+
def resolve(did, no_doc):
173
173
+
"""Resolve a did:cow DID to its current state and wrapped DID document.
129
174
130
175
\b
131
176
DID format: did:cow:<controller_address>:<method>:<id>
···
142
187
143
188
controller, wrapped_did = contract.functions.cows(cow_hash).call()
144
189
190
190
+
if wrapped_did == ":":
191
191
+
click.echo("status: deactivated")
192
192
+
return
193
193
+
145
194
if wrapped_did == "":
146
195
# No on-chain state — initial values from the DID string are authoritative
147
147
-
click.echo(f"status: not yet registered on-chain")
196
196
+
click.echo("status: not yet registered on-chain")
148
197
click.echo(f"wrapped: did:{initial_wrapped} (from DID)")
149
198
click.echo(f"controller: {_controller_address(controller_hex)} (initial)")
150
150
-
elif wrapped_did == ":":
151
151
-
click.echo(f"status: deactivated")
199
199
+
current_wrapped = initial_wrapped
152
200
else:
153
153
-
click.echo(f"status: active")
201
201
+
click.echo("status: active")
154
202
click.echo(f"wrapped: did:{wrapped_did}")
155
203
click.echo(f"controller: {controller}")
204
204
+
current_wrapped = wrapped_did
205
205
+
206
206
+
if not no_doc:
207
207
+
click.echo("")
208
208
+
url, doc = _resolve_did_doc(current_wrapped)
209
209
+
click.echo(f"resolved from: {url}")
210
210
+
click.echo(json.dumps(doc, indent=2))
156
211
157
212
158
213
@cli.command("update-wrapped")
···
176
231
_controller_address(controller_hex),
177
232
initial_wrapped,
178
233
new_wrapped,
179
179
-
).build_transaction({})
234
234
+
).build_transaction({"from": account.address})
180
235
181
236
_send(w3, account, tx)
182
237
click.echo(f"wrapped: did:{new_wrapped}")
···
202
257
_controller_address(controller_hex),
203
258
initial_wrapped,
204
259
Web3.to_checksum_address(new_controller),
205
205
-
).build_transaction({})
260
260
+
).build_transaction({"from": account.address})
206
261
207
262
_send(w3, account, tx)
208
263
click.echo(f"controller: {Web3.to_checksum_address(new_controller)}")
···
224
279
initial_wrapped,
225
280
).call()
226
281
227
227
-
tx = contract.functions.deactivate(cow_hash).build_transaction({})
282
282
+
tx = contract.functions.deactivate(cow_hash).build_transaction({"from": account.address})
228
283
229
284
_send(w3, account, tx)
230
285
click.echo("deactivated.")
+1
cli/requirements.txt
View file
Reviewed
···
1
1
web3>=7.0.0
2
2
click>=8.0.0
3
3
python-dotenv>=1.0.0
4
4
+
requests>=2.31.0