Repository: TBD54566975/ssi-service
Pull request #360 (2023-03-28 02:09:00)

refactoring DID resolution
- unified handlers
- removed cycles by having `internal` not import anything from `service` (we should keep this pattern)

codecov-commenter: ## [Codecov](https://codecov.io/gh/TBD54566975/ssi-service/pull/360?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) Report
> Merging [#360](https://codecov.io/gh/TBD54566975/ssi-service/pull/360?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (dc23670) into [main](https://codecov.io/gh/TBD54566975/ssi-service/commit/b0430d833a58b7e5d305f1579a0c40d3c9249ef8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (b0430d8) will **increase** coverage by `0.03%`.
> The diff coverage is `0.00%`.

```diff
@@            Coverage Diff             @@
##             main     #360      +/-   ##
==========================================
+ Coverage   19.08%   19.12%   +0.03%     
==========================================
  Files          38       38              
  Lines        4370     4361       -9     
==========================================
  Hits          834      834              
+ Misses       3413     3404       -9     
  Partials      123      123              
```


| [Impacted Files](https://codecov.io/gh/TBD54566975/ssi-service/pull/360?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) | Coverage Δ | |
|---|---|---|
| [internal/did/access.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/360?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-aW50ZXJuYWwvZGlkL2FjY2Vzcy5nbw==) | `22.22% <0.00%> (+2.86%)` | :arrow_up: |
| [internal/util/util.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/360?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-aW50ZXJuYWwvdXRpbC91dGlsLmdv) | `0.00% <0.00%> (ø)` | |
| [pkg/service/credential/service.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/360?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZpY2UvY3JlZGVudGlhbC9zZXJ2aWNlLmdv) | `0.00% <0.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975)


Patch:
diff --git a/go.mod b/go.mod
index ca9b13b..45a86e1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.20
 
 require (
 	github.com/BurntSushi/toml v1.2.1
-	github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230327190942-a55dfe2b54ff
+	github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230328175208-0ce55b2b0262
 	github.com/alicebob/miniredis/v2 v2.30.1
 	github.com/ardanlabs/conf v1.5.0
 	github.com/benbjohnson/clock v1.3.0
@@ -54,7 +54,7 @@ require (
 	github.com/go-logr/logr v1.2.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-playground/validator/v10 v10.12.0 // indirect
-	github.com/hyperledger/aries-framework-go v0.1.9-0.20230217102417-a948231f8452 // indirect
+	github.com/hyperledger/aries-framework-go v0.1.9 // indirect
 	github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e // indirect
 	github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69 // indirect
 	github.com/leodido/go-urn v1.2.2 // indirect
@@ -63,7 +63,7 @@ require (
 	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/lestrrat-go/httprc v1.0.4 // indirect
 	github.com/lestrrat-go/iter v1.0.2 // indirect
-	github.com/lestrrat-go/jwx/v2 v2.0.8 // indirect
+	github.com/lestrrat-go/jwx/v2 v2.0.9 // indirect
 	github.com/lestrrat-go/option v1.0.1 // indirect
 	github.com/multiformats/go-base32 v0.1.0 // indirect
 	github.com/multiformats/go-base36 v0.2.0 // indirect
diff --git a/go.sum b/go.sum
index c9daf6d..aa9fbb0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230327190942-a55dfe2b54ff h1:rn85BWOftgYQ69+7bnZxKqVUcwfBWTF+xLpe0yFBoik=
-github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230327190942-a55dfe2b54ff/go.mod h1:MAaC9QetTLZ2BoX54oknA2eUmMfwgxOwB+lp0MOhZgo=
+github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230328165935-a9e587d94ba5 h1:dWIS0axYmcuSa+4xK0W5hoqINoqjC1Vh4DL4sxfUEYE=
+github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230328165935-a9e587d94ba5/go.mod h1:Ro9+HDADuYcLnCYKIL8pu92XWITKetTPOqfwLThplO0=
+github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230328175208-0ce55b2b0262 h1:mMd9kTYtqEHAbiYwtnH94qyY4Meanp0mWkOktrZyVv4=
+github.com/TBD54566975/ssi-sdk v0.0.3-alpha.0.20230328175208-0ce55b2b0262/go.mod h1:Ro9+HDADuYcLnCYKIL8pu92XWITKetTPOqfwLThplO0=
 github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
 github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
@@ -53,7 +55,6 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
 github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
 github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -66,8 +67,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
-github.com/hyperledger/aries-framework-go v0.1.9-0.20230217102417-a948231f8452 h1:0MVQG7a8A6hDuvyV0EWh8KkmAIS3YsWTRt+qFJtxvaQ=
-github.com/hyperledger/aries-framework-go v0.1.9-0.20230217102417-a948231f8452/go.mod h1:qrOxEGVsu8M2RahaJgM8nz9AcAHjR/dKd1JIJ3ieJhY=
+github.com/hyperledger/aries-framework-go v0.1.9 h1:qwfjV/zGaH745AMyQwpL3296uRCbM3sJPVFzgW7EBWk=
+github.com/hyperledger/aries-framework-go v0.1.9/go.mod h1:qrOxEGVsu8M2RahaJgM8nz9AcAHjR/dKd1JIJ3ieJhY=
 github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e h1:SxbXlF39661T9w/L9PhVdtbJfJ51Pm4JYEEW6XfZHEQ=
 github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e/go.mod h1:oryUyWb23l/a3tAP9KW+GBbfcfqp9tZD4y5hSkFrkqI=
 github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
@@ -93,8 +94,8 @@ github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzlt
 github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
 github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA=
 github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
-github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9HR/Q=
-github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0=
+github.com/lestrrat-go/jwx/v2 v2.0.9 h1:TRX4Q630UXxPVLvP5vGaqVJO7S+0PE6msRZUsFSBoC8=
+github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifbJUydCTPmgM=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
@@ -149,6 +150,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
 github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
 go.einride.tech/aip v0.60.0 h1:h6bgabZ5BCfAptbGex8jbh3VvPBRLa6xq+pQ1CAjHYw=
@@ -171,26 +173,52 @@ go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDs
 go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ=
 go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
 go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 h1:muK+gVBJBfFb4SejshDBlN2/UgxCCOKH9Y34ljqEGOc=
 google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
diff --git a/internal/credential/verification.go b/internal/credential/verification.go
index 78bee39..d7317a8 100644
--- a/internal/credential/verification.go
+++ b/internal/credential/verification.go
@@ -8,9 +8,9 @@ import (
 	credsdk "github.com/TBD54566975/ssi-sdk/credential"
 	"github.com/TBD54566975/ssi-sdk/credential/signing"
 	"github.com/TBD54566975/ssi-sdk/credential/verification"
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	"github.com/goccy/go-json"
 	"github.com/pkg/errors"
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
 
 	didint "github.com/tbd54566975/ssi-service/internal/did"
 	"github.com/tbd54566975/ssi-service/internal/keyaccess"
@@ -20,13 +20,13 @@ import (
 
 type Verifier struct {
 	verifier       *verification.CredentialVerifier
-	didResolver    resolve.Resolver
+	didResolver    didsdk.Resolver
 	schemaResolver schema.Resolution
 }
 
 // NewCredentialVerifier creates a new credential verifier which executes both signature and static verification checks.
 // In the future the set of verification checks will be configurable.
-func NewCredentialVerifier(didResolver resolve.Resolver, schemaResolver schema.Resolution) (*Verifier, error) {
+func NewCredentialVerifier(didResolver didsdk.Resolver, schemaResolver schema.Resolution) (*Verifier, error) {
 	if didResolver == nil {
 		return nil, errors.New("didResolver cannot be nil")
 	}
@@ -122,8 +122,7 @@ func (v Verifier) VerifyJWT(ctx context.Context, did string, token keyaccess.JWT
 	// construct a signature verifier from the verification information
 	verifier, err := keyaccess.NewJWKKeyAccessVerifier(kid, pubKey)
 	if err != nil {
-		errMsg := fmt.Sprintf("could not create verifier for kid %s", kid)
-		return util.LoggingErrorMsg(err, errMsg)
+		return util.LoggingErrorMsgf(err, "could not create verifier for kid %s", kid)
 	}
 
 	// verify the signature on the credential
@@ -140,7 +139,6 @@ func (v Verifier) VerifyJWT(ctx context.Context, did string, token keyaccess.JWT
 func (v Verifier) resolveCredentialIssuerKey(ctx context.Context, credential credsdk.VerifiableCredential) (kid string, pubKey crypto.PublicKey, err error) {
 	issuerDID := credential.Issuer.(string)
 	return didint.ResolveKeyForDID(ctx, v.didResolver, issuerDID)
-
 }
 
 // staticVerificationChecks runs a set of static verification checks on the credential as per the credential
diff --git a/internal/did/access.go b/internal/did/access.go
index 885b3e7..ca0ceca 100644
--- a/internal/did/access.go
+++ b/internal/did/access.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/TBD54566975/ssi-sdk/cryptosuite"
 	didsdk "github.com/TBD54566975/ssi-sdk/did"
+	"github.com/TBD54566975/ssi-sdk/util"
 	"github.com/goccy/go-json"
 	"github.com/lestrrat-go/jwx/jwk"
 	"github.com/mr-tron/base58"
@@ -15,7 +16,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
+	"github.com/tbd54566975/ssi-service/internal/keyaccess"
 )
 
 // GetVerificationInformation resolves a DID and provides a kid and public key needed for data verification
@@ -88,6 +89,48 @@ func extractKeyFromVerificationMethod(method didsdk.VerificationMethod) (pubKey
 	return
 }
 
+// ResolveKeyForDID resolves a public key from a DID.
+func ResolveKeyForDID(ctx context.Context, resolver didsdk.Resolver, did string) (kid string, pubKey crypto.PublicKey, err error) {
+	resolved, err := resolver.Resolve(ctx, did, nil)
+	if err != nil {
+		err = errors.Wrapf(err, "resolving DID: %s", did)
+		return "", nil, err
+	}
+
+	// next, get the verification information (key) from the did document
+	kid, pubKey, err = GetVerificationInformation(resolved.Document, "")
+	if err != nil {
+		err = errors.Wrapf(err, "getting verification information from DID Document: %s", did)
+		return "", nil, err
+	}
+	return kid, pubKey, err
+}
+
+// VerifyTokenFromDID verifies that the information in the token was digitally signed by the public key associated with
+// the public key of the verification method of the did's document. The passed in resolver is used to map from the did
+// to the did document.
+func VerifyTokenFromDID(ctx context.Context, resolver didsdk.Resolver, did string, token keyaccess.JWT) error {
+	resolved, err := resolver.Resolve(ctx, did)
+	if err != nil {
+		return errors.Wrapf(err, "resolving DID: %s", did)
+	}
+
+	// get the verification information from the DID document
+	kid, pubKey, err := GetVerificationInformation(resolved.Document, "")
+	if err != nil {
+		return errors.Wrapf(err, "getting verification information from the DID document: %s", did)
+	}
+
+	verifier, err := keyaccess.NewJWKKeyAccessVerifier(kid, pubKey)
+	if err != nil {
+		return util.LoggingErrorMsg(err, "could not create application verifier")
+	}
+	if err = verifier.Verify(token); err != nil {
+		return util.LoggingErrorMsg(err, "could not verify the application's signature")
+	}
+	return nil
+}
+
 // multibaseToPubKey converts a multibase encoded public key to public key bytes for known multibase encodings
 func multibaseToPubKeyBytes(mb string) ([]byte, error) {
 	if mb == "" {
@@ -102,7 +145,7 @@ func multibaseToPubKeyBytes(mb string) ([]byte, error) {
 		return nil, err
 	}
 	if encoding != didsdk.Base58BTCMultiBase {
-		err := fmt.Errorf("expected %d encoding but found %d", didsdk.Base58BTCMultiBase, encoding)
+		err = fmt.Errorf("expected %d encoding but found %d", didsdk.Base58BTCMultiBase, encoding)
 		logrus.WithError(err).Error()
 		return nil, err
 	}
@@ -118,20 +161,3 @@ func multibaseToPubKeyBytes(mb string) ([]byte, error) {
 	pubKeyBytes := decoded[n:]
 	return pubKeyBytes, nil
 }
-
-// ResolveKeyForDID resolves a public key from a DID.
-func ResolveKeyForDID(ctx context.Context, resolver resolve.Resolver, did string) (kid string, pubKey crypto.PublicKey, err error) {
-	resolved, err := resolver.Resolve(ctx, did, nil)
-	if err != nil {
-		err = errors.Wrapf(err, "failed to resolve did: %s", did)
-		return
-	}
-
-	// next, get the verification information (key) from the did document
-	kid, pubKey, err = GetVerificationInformation(resolved.Document, "")
-	if err != nil {
-		err = errors.Wrapf(err, "failed to get verification information from the did document: %s", did)
-		return
-	}
-	return
-}
diff --git a/internal/did/resolver.go b/internal/did/resolver.go
index e77178b..0b92845 100644
--- a/internal/did/resolver.go
+++ b/internal/did/resolver.go
@@ -7,12 +7,12 @@ import (
 	"github.com/pkg/errors"
 )
 
-// BuildResolver builds a DID resolver from a list of methods to support resolution for
-func BuildResolver(methods []string) (*didsdk.Resolver, error) {
+// BuildMultiMethodResolver builds a multi method DID resolver from a list of methods to support resolution for
+func BuildMultiMethodResolver(methods []string) (*didsdk.MultiMethodResolver, error) {
 	if len(methods) == 0 {
 		return nil, errors.New("no methods provided")
 	}
-	resolvers := make([]didsdk.Resolution, 0, len(methods))
+	resolvers := make([]didsdk.Resolver, 0, len(methods))
 	for _, method := range methods {
 		resolver, err := getKnownResolver(method)
 		if err != nil {
@@ -27,7 +27,7 @@ func BuildResolver(methods []string) (*didsdk.Resolver, error) {
 }
 
 // all possible resolvers for the DID service
-func getKnownResolver(method string) (didsdk.Resolution, error) {
+func getKnownResolver(method string) (didsdk.Resolver, error) {
 	switch didsdk.Method(method) {
 	case didsdk.KeyMethod:
 		return new(didsdk.KeyResolver), nil
diff --git a/internal/util/util.go b/internal/util/util.go
index 69a0c2c..b8b2f1e 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -4,17 +4,21 @@ import (
 	"fmt"
 	"strings"
 
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
 // GetMethodForDID gets a DID method from a did, the second part of the did (e.g. did:test:abcd, the method is 'test')
-func GetMethodForDID(did string) (string, error) {
+func GetMethodForDID(did string) (didsdk.Method, error) {
 	split := strings.Split(did, ":")
 	if len(split) < 3 {
-		return "", fmt.Errorf("malformed did: %s", did)
+		return "", errors.New("malformed did: did has fewer than three parts")
 	}
-	return split[1], nil
+	if split[0] != "did" {
+		return "", errors.New("malformed did: did must start with `did`")
+	}
+	return didsdk.Method(split[1]), nil
 }
 
 // LoggingError is a utility to combine logging an error, and returning and error
diff --git a/magefile.go b/magefile.go
index 01f4ecd..f27e5e9 100644
--- a/magefile.go
+++ b/magefile.go
@@ -26,7 +26,7 @@ var (
 
 // Build builds the library.
 func Build() error {
-	fmt.Println("Building library...")
+	fmt.Println("Building project...")
 	return sh.Run(Go, "build", "-tags", "jwx_es256k", "./...")
 }
 
diff --git a/pkg/jwt/verification.go b/pkg/jwt/verification.go
deleted file mode 100644
index 55baacc..0000000
--- a/pkg/jwt/verification.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package jwt
-
-import (
-	"context"
-
-	"github.com/pkg/errors"
-	didint "github.com/tbd54566975/ssi-service/internal/did"
-	"github.com/tbd54566975/ssi-service/internal/keyaccess"
-	"github.com/tbd54566975/ssi-service/internal/util"
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
-)
-
-// VerifyTokenFromDID verifies that the information in the token was digitally signed by the public key associated with
-// the public key of the verification method of the did's document. The passed in resolver is used to map from the did
-// to the did document.
-func VerifyTokenFromDID(ctx context.Context, did string, token keyaccess.JWT, s resolve.Resolver) error {
-	kid, pubKey, err := didint.ResolveKeyForDID(ctx, s, did)
-	if err != nil {
-		return errors.Wrapf(err, "failed to resolve applicant's did: %s", did)
-	}
-	verifier, err := keyaccess.NewJWKKeyAccessVerifier(kid, pubKey)
-	if err != nil {
-		return util.LoggingErrorMsg(err, "could not create application verifier")
-	}
-	if err := verifier.Verify(token); err != nil {
-		return util.LoggingErrorMsg(err, "could not verify the application's signature")
-	}
-	return nil
-}
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 18b2679..c0c095b 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -9,6 +9,7 @@ import (
 	"path"
 
 	"github.com/sirupsen/logrus"
+
 	"github.com/tbd54566975/ssi-service/config"
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/server/framework"
diff --git a/pkg/service/credential/service.go b/pkg/service/credential/service.go
index 82cb0e6..a9fadda 100644
--- a/pkg/service/credential/service.go
+++ b/pkg/service/credential/service.go
@@ -9,15 +9,16 @@ import (
 	"github.com/TBD54566975/ssi-sdk/credential"
 	schemalib "github.com/TBD54566975/ssi-sdk/credential/schema"
 	statussdk "github.com/TBD54566975/ssi-sdk/credential/status"
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	sdkutil "github.com/TBD54566975/ssi-sdk/util"
 	"github.com/google/uuid"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+
 	"github.com/tbd54566975/ssi-service/config"
 	credint "github.com/tbd54566975/ssi-service/internal/credential"
 	"github.com/tbd54566975/ssi-service/internal/keyaccess"
 	"github.com/tbd54566975/ssi-service/internal/util"
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
 	"github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/keystore"
 	"github.com/tbd54566975/ssi-service/pkg/service/schema"
@@ -65,7 +66,7 @@ func (s Service) Config() config.CredentialServiceConfig {
 	return s.config
 }
 
-func NewCredentialService(config config.CredentialServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service, didResolver resolve.Resolver, schema *schema.Service) (*Service, error) {
+func NewCredentialService(config config.CredentialServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service, didResolver didsdk.Resolver, schema *schema.Service) (*Service, error) {
 	credentialStorage, err := NewCredentialStorage(s)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not instantiate storage for the credential service")
diff --git a/pkg/service/did/handler.go b/pkg/service/did/handler.go
new file mode 100644
index 0000000..7ff5482
--- /dev/null
+++ b/pkg/service/did/handler.go
@@ -0,0 +1,74 @@
+package did
+
+import (
+	"context"
+
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
+	"github.com/TBD54566975/ssi-sdk/util"
+	"github.com/pkg/errors"
+)
+
+// MethodHandler describes the functionality of *all* possible DID service, regardless of method
+// TODO(gabe) consider smaller/more composable interfaces and promoting reusability across methods
+// https://github.com/TBD54566975/ssi-service/issues/362
+type MethodHandler interface {
+	CreateDID(ctx context.Context, request CreateDIDRequest) (*CreateDIDResponse, error)
+	GetDID(ctx context.Context, request GetDIDRequest) (*GetDIDResponse, error)
+	GetDIDs(ctx context.Context, method didsdk.Method) (*GetDIDsResponse, error)
+	SoftDeleteDID(ctx context.Context, request DeleteDIDRequest) error
+}
+
+// NewHandlerResolver creates a new HandlerResolver from a map of MethodHandlers which are used to resolve DIDs
+// stored in our database
+func NewHandlerResolver(handlers map[didsdk.Method]MethodHandler) (*didsdk.MultiMethodResolver, error) {
+	if len(handlers) == 0 {
+		return nil, util.LoggingNewError("no handlers provided")
+	}
+
+	methodResolvers := make([]didsdk.Resolver, 0, len(handlers))
+	for method, handler := range handlers {
+		methodResolver := resolverFromHandler(method, handler)
+		methodResolvers = append(methodResolvers, methodResolver)
+	}
+
+	multiMethodResolver, err := didsdk.NewResolver(methodResolvers...)
+	if err != nil {
+		return nil, errors.Wrap(err, "creating multi-method resolver")
+	}
+
+	return multiMethodResolver, nil
+}
+
+// handlerResolver is a DID resolver that uses a MethodHandler to resolve DIDs
+type handlerResolver struct {
+	handler MethodHandler
+	method  didsdk.Method
+}
+
+func (h handlerResolver) Resolve(ctx context.Context, did string, _ ...didsdk.ResolutionOption) (*didsdk.ResolutionResult, error) {
+	method, err := didsdk.GetMethodForDID(did)
+	if err != nil {
+		return nil, errors.Wrap(err, "getting method from DID")
+	}
+
+	if method != h.method {
+		return nil, errors.Errorf("invalid method %s for handler %s", method, h.method)
+	}
+
+	gotDIDResponse, err := h.handler.GetDID(ctx, GetDIDRequest{
+		Method: h.method,
+		ID:     did,
+	})
+	if err != nil {
+		return nil, errors.Wrap(err, "getting DID from handler")
+	}
+	return &didsdk.ResolutionResult{Document: gotDIDResponse.DID}, nil
+}
+
+func (h handlerResolver) Methods() []didsdk.Method {
+	return []didsdk.Method{h.method}
+}
+
+func resolverFromHandler(method didsdk.Method, handler MethodHandler) didsdk.Resolver {
+	return &handlerResolver{handler: handler, method: method}
+}
diff --git a/pkg/service/did/key.go b/pkg/service/did/key.go
index b692d5d..a3e9592 100644
--- a/pkg/service/did/key.go
+++ b/pkg/service/did/key.go
@@ -23,7 +23,6 @@ type keyDIDHandler struct {
 }
 
 func (h *keyDIDHandler) CreateDID(ctx context.Context, request CreateDIDRequest) (*CreateDIDResponse, error) {
-
 	logrus.Debugf("creating DID: %+v", request)
 
 	// create the DID
@@ -76,7 +75,6 @@ func (h *keyDIDHandler) CreateDID(ctx context.Context, request CreateDIDRequest)
 }
 
 func (h *keyDIDHandler) GetDID(ctx context.Context, request GetDIDRequest) (*GetDIDResponse, error) {
-
 	logrus.Debugf("getting DID: %+v", request)
 
 	id := request.ID
@@ -91,7 +89,6 @@ func (h *keyDIDHandler) GetDID(ctx context.Context, request GetDIDRequest) (*Get
 }
 
 func (h *keyDIDHandler) GetDIDs(ctx context.Context, method did.Method) (*GetDIDsResponse, error) {
-
 	logrus.Debugf("getting DIDs for method: %s", method)
 
 	gotDIDs, err := h.storage.GetDIDs(ctx, string(method))
diff --git a/pkg/service/did/model.go b/pkg/service/did/model.go
index 7cb944f..2345644 100644
--- a/pkg/service/did/model.go
+++ b/pkg/service/did/model.go
@@ -1,6 +1,8 @@
 package did
 
 import (
+	gocrypto "crypto"
+
 	"github.com/TBD54566975/ssi-sdk/crypto"
 	didsdk "github.com/TBD54566975/ssi-sdk/did"
 )
@@ -43,6 +45,16 @@ type GetDIDResponse struct {
 	DID didsdk.Document `json:"did"`
 }
 
+type GetKeyFromDIDRequest struct {
+	ID    string `json:"id" validate:"required"`
+	KeyID string `json:"keyId,omitempty"`
+}
+
+type GetKeyFromDIDResponse struct {
+	KeyID     string             `json:"keyId"`
+	PublicKey gocrypto.PublicKey `json:"publicKey"`
+}
+
 type GetDIDsRequest struct {
 	Method didsdk.Method `json:"method" validate:"required"`
 }
diff --git a/pkg/service/did/resolution/resolver.go b/pkg/service/did/resolution/resolver.go
new file mode 100644
index 0000000..d8fca24
--- /dev/null
+++ b/pkg/service/did/resolution/resolver.go
@@ -0,0 +1,103 @@
+package resolution
+
+import (
+	"context"
+	"fmt"
+
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+
+	didint "github.com/tbd54566975/ssi-service/internal/did"
+	utilint "github.com/tbd54566975/ssi-service/internal/util"
+)
+
+// ServiceResolver is a resolver that can resolve DIDs using a combination of local and universal resolvers.
+type ServiceResolver struct {
+	resolutionMethods []string
+	hr                didsdk.Resolver
+	lr                didsdk.Resolver
+	ur                *universalResolver
+}
+
+var _ didsdk.Resolver = (*ServiceResolver)(nil)
+
+// NewServiceResolver creates a new ServiceResolver instance which can resolve DIDs using a combination of local and
+// universal resolvers.
+func NewServiceResolver(handlerResolver didsdk.Resolver, resolutionMethods []string, universalResolverURL string) (*ServiceResolver, error) {
+	if len(resolutionMethods) == 0 {
+		return nil, errors.New("no resolution methods configured")
+	}
+
+	// instantiate sdk resolver
+	localResolver, err := didint.BuildMultiMethodResolver(resolutionMethods)
+	if err != nil {
+		return nil, errors.Wrap(err, "instantiating SDK DID resolver")
+	}
+
+	// instantiate universal resolver
+	var ur *universalResolver
+	if universalResolverURL != "" {
+		ur, err = newUniversalResolver(universalResolverURL)
+		if err != nil {
+			return nil, errors.Wrap(err, "instantiating universal resolver")
+		}
+	}
+
+	return &ServiceResolver{
+		resolutionMethods: resolutionMethods,
+		hr:                handlerResolver,
+		lr:                localResolver,
+		ur:                ur,
+	}, nil
+}
+
+// Resolve resolves a DID using a combination of local and universal resolvers. The ordering is as follows:
+// 1. Try to resolve with the handlers we have, wrapping the resulting DID in resolution result
+// 2. Try to resolve with the local resolver
+// 3. Try to resolve with the universal resolver
+// TODO(gabe) avoid caching DIDs that should be externally resolved https://github.com/TBD54566975/ssi-service/issues/361
+func (sr *ServiceResolver) Resolve(ctx context.Context, did string, opts ...didsdk.ResolutionOption) (*didsdk.ResolutionResult, error) {
+	// check the did is valid
+	if _, err := utilint.GetMethodForDID(did); err != nil {
+		return nil, errors.Wrap(err, "getting method DID")
+	}
+
+	// first, try to resolve with the handlers we have
+	if sr.hr != nil {
+		handlersResolvedDID, err := sr.hr.Resolve(ctx, did, opts...)
+		if err == nil {
+			return handlersResolvedDID, nil
+		}
+		logrus.WithError(err).Error("error resolving DID with handler resolver")
+	}
+
+	// next, try to resolve with the local resolver
+	if sr.lr != nil {
+		locallyResolvedDID, err := sr.lr.Resolve(ctx, did, opts...)
+		if err == nil {
+			return locallyResolvedDID, nil
+		}
+		logrus.WithError(err).Error("error resolving DID with local resolver")
+	}
+
+	// finally, resolution with the universal resolver
+	if sr.ur != nil {
+		universallyResolvedDID, err := sr.ur.Resolve(ctx, did, opts...)
+		if err == nil {
+			return universallyResolvedDID, nil
+
+		}
+		logrus.WithError(err).Error("error resolving DID with universal resolver")
+	}
+
+	return nil, fmt.Errorf("unable to resolve DID %s", did)
+}
+
+func (sr *ServiceResolver) Methods() []didsdk.Method {
+	methods := make([]didsdk.Method, 0, len(sr.resolutionMethods))
+	for _, m := range sr.resolutionMethods {
+		methods = append(methods, didsdk.Method(m))
+	}
+	return methods
+}
diff --git a/pkg/service/did/resolution/universal.go b/pkg/service/did/resolution/universal.go
new file mode 100644
index 0000000..e0730bd
--- /dev/null
+++ b/pkg/service/did/resolution/universal.go
@@ -0,0 +1,94 @@
+package resolution
+
+import (
+	"bufio"
+	"context"
+	"io"
+	"net/http"
+
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
+	"github.com/goccy/go-json"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
+)
+
+// universalResolver is a struct that implements the Resolver interface. It calls the universal resolver endpoint
+// to resolve any DID according to https://github.com/decentralized-identity/universal-resolver.
+type universalResolver struct {
+	client           *http.Client
+	url              string
+	supportedMethods []didsdk.Method
+}
+
+var _ didsdk.Resolver = (*universalResolver)(nil)
+
+func newUniversalResolver(url string) (*universalResolver, error) {
+	if url == "" {
+		return nil, errors.New("universal resolver url cannot be empty")
+	}
+	return &universalResolver{
+		client: &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)},
+		url:    url,
+	}, nil
+}
+
+// Resolve results resolution results by doing a GET on <url>/1.0.identifiers/<did>.
+func (ur *universalResolver) Resolve(ctx context.Context, did string, _ ...didsdk.ResolutionOption) (*didsdk.ResolutionResult, error) {
+	url := ur.url + "/1.0/identifiers/" + did
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+	if err != nil {
+		return nil, errors.Wrap(err, "creating request")
+	}
+
+	resp, err := ur.client.Do(req)
+	if err != nil {
+		return nil, errors.Wrap(err, "performing http get")
+	}
+
+	respBody, err := io.ReadAll(bufio.NewReader(resp.Body))
+	if err != nil {
+		return nil, err
+	}
+	var result didsdk.ResolutionResult
+	if err = json.Unmarshal(respBody, &result); err != nil {
+		return nil, errors.Wrap(err, "unmarshalling JSON")
+	}
+	return &result, nil
+}
+
+// Methods returns the methods that this resolver supports
+// as per https://github.com/decentralized-identity/universal-resolver/blob/main/swagger/api.yml#L121
+func (ur *universalResolver) Methods() []didsdk.Method {
+	// check if we've cached the methods
+	if len(ur.supportedMethods) > 0 {
+		return ur.supportedMethods
+	}
+
+	url := ur.url + "/1.0/methods"
+	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
+	if err != nil {
+		logrus.WithError(err).Error("Failed to create request for universal resolver methods")
+	}
+
+	resp, err := ur.client.Do(req)
+	if err != nil {
+		logrus.WithError(err).Error("Failed to perform http get for universal resolver methods")
+		return nil
+	}
+
+	respBody, err := io.ReadAll(bufio.NewReader(resp.Body))
+	if err != nil {
+		logrus.WithError(err).Error("Failed to read response body for universal resolver methods")
+		return nil
+	}
+	var methods []didsdk.Method
+	if err = json.Unmarshal(respBody, &methods); err != nil {
+		logrus.WithError(err).Error("Failed to unmarshal response body for universal resolver methods")
+		return nil
+	}
+
+	// update the method cache
+	ur.supportedMethods = methods
+	return methods
+}
diff --git a/pkg/service/did/resolve/universal.go b/pkg/service/did/resolve/universal.go
deleted file mode 100644
index 2a2c3b6..0000000
--- a/pkg/service/did/resolve/universal.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package resolve
-
-import (
-	"bufio"
-	"context"
-	"io"
-	"net/http"
-
-	didsdk "github.com/TBD54566975/ssi-sdk/did"
-	"github.com/goccy/go-json"
-	"github.com/pkg/errors"
-)
-
-// UniversalResolver is a struct that implements the Resolver interface. It calls the universal resolver endpoint
-// to resolve any DID according to https://github.com/decentralized-identity/universal-resolver.
-type UniversalResolver struct {
-	Client http.Client
-	URL    string
-}
-
-type Resolver interface {
-	Resolve(ctx context.Context, did string, opts ...didsdk.ResolutionOptions) (*didsdk.ResolutionResult, error)
-}
-
-// Resolve results resolution results by doing a GET on <URL>/1.0.identifiers/<did>.
-func (ur UniversalResolver) Resolve(ctx context.Context, did string, _ ...didsdk.ResolutionOptions) (*didsdk.ResolutionResult, error) {
-	url := ur.URL + "/1.0/identifiers/" + did
-	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "creating request")
-	}
-
-	resp, err := ur.Client.Do(req)
-	if err != nil {
-		return nil, errors.Wrap(err, "performing http get")
-	}
-
-	respBody, err := io.ReadAll(bufio.NewReader(resp.Body))
-	if err != nil {
-		return nil, err
-	}
-	var result didsdk.ResolutionResult
-	if err = json.Unmarshal(respBody, &result); err != nil {
-		return nil, errors.Wrap(err, "unmarshalling JSON")
-	}
-	return &result, nil
-}
-
-// LocalResolver is an implementation of Resolver that passes through the parameters into the sdk implementation that
-// resolves DIDs. This is done because, when this is being written, the didsdk.Resolution interface does not let callers
-// pass in their own context.
-type LocalResolver struct {
-	*didsdk.Resolver
-}
-
-func (lr LocalResolver) Resolve(_ context.Context, did string, opts ...didsdk.ResolutionOptions) (*didsdk.ResolutionResult, error) {
-	return lr.Resolver.Resolve(did, opts...)
-}
diff --git a/pkg/service/did/service.go b/pkg/service/did/service.go
index a40d079..42bbc48 100644
--- a/pkg/service/did/service.go
+++ b/pkg/service/did/service.go
@@ -3,7 +3,6 @@ package did
 import (
 	"context"
 	"fmt"
-	"net/http"
 
 	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	sdkutil "github.com/TBD54566975/ssi-sdk/util"
@@ -12,22 +11,22 @@ import (
 	"github.com/tbd54566975/ssi-service/config"
 	"github.com/tbd54566975/ssi-service/internal/did"
 	"github.com/tbd54566975/ssi-service/internal/util"
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
+	"github.com/tbd54566975/ssi-service/pkg/service/did/resolution"
 	"github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/keystore"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
 
 type Service struct {
-	config        config.DIDServiceConfig
-	storage       *Storage
-	localResolver *resolve.LocalResolver
-
-	methodToResolver map[string]resolve.Resolver
+	config  config.DIDServiceConfig
+	storage *Storage
 
 	// supported DID methods
 	handlers map[didsdk.Method]MethodHandler
 
+	// resolver for DID methods
+	resolver *resolution.ServiceResolver
+
 	// external dependencies
 	keyStore *keystore.Service
 }
@@ -48,7 +47,7 @@ func (s *Service) Status() framework.Status {
 	if s.keyStore == nil {
 		ae.AppendString("no key store service configured")
 	}
-	if s.localResolver == nil {
+	if s.resolver == nil {
 		ae.AppendString("no resolver configured")
 	}
 	if !ae.IsEmpty() {
@@ -64,8 +63,8 @@ func (s *Service) Config() config.DIDServiceConfig {
 	return s.config
 }
 
-func (s *Service) GetResolver() resolve.Resolver {
-	return s
+func (s *Service) GetResolver() didsdk.Resolver {
+	return s.resolver
 }
 
 func NewDIDService(config config.DIDServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service) (*Service, error) {
@@ -74,57 +73,38 @@ func NewDIDService(config config.DIDServiceConfig, s storage.ServiceStorage, key
 		return nil, errors.Wrap(err, "could not instantiate DID storage for the DID service")
 	}
 
-	// instantiate DID resolver
-	sdkResolver, err := did.BuildResolver(config.ResolutionMethods)
-	if err != nil {
-		return nil, errors.Wrap(err, "could not instantiate DID resolver")
-	}
-
 	service := Service{
-		storage:          didStorage,
-		handlers:         make(map[didsdk.Method]MethodHandler),
-		keyStore:         keyStore,
-		localResolver:    &resolve.LocalResolver{Resolver: sdkResolver},
-		methodToResolver: make(map[string]resolve.Resolver),
-	}
-
-	for _, sm := range service.localResolver.SupportedMethods() {
-		service.methodToResolver[sm.String()] = service.localResolver
-	}
-
-	ur := &resolve.UniversalResolver{
-		Client: http.Client{},
-		URL:    config.UniversalResolverURL,
-	}
-	for _, urm := range config.UniversalResolverMethods {
-		service.methodToResolver[urm] = ur
+		storage:  didStorage,
+		handlers: make(map[didsdk.Method]MethodHandler),
+		keyStore: keyStore,
 	}
 
 	// instantiate all handlers for DID methods
 	for _, m := range config.Methods {
 		if err = service.instantiateHandlerForMethod(didsdk.Method(m)); err != nil {
-			return nil, errors.Wrap(err, "could not instantiate DID service")
+			return nil, errors.Wrap(err, "instantiating DID service")
 		}
 	}
 
+	// create handler resolver first, which wraps our handlers as a resolver
+	hr, err := NewHandlerResolver(service.handlers)
+	if err != nil {
+		return nil, errors.Wrap(err, "instantiating handler resolver")
+	}
+
+	// instantiate DID resolver
+	resolver, err := resolution.NewServiceResolver(hr, config.ResolutionMethods, config.UniversalResolverURL)
+	if err != nil {
+		return nil, errors.Wrap(err, "instantiating DID resolver")
+	}
+	service.resolver = resolver
+
 	if !service.Status().IsReady() {
 		return nil, errors.New(service.Status().Message)
 	}
 	return &service, nil
 }
 
-type MethodHandlerProvider interface {
-	NewMethodHandler(s *Storage, ks *keystore.Service) (MethodHandler, error)
-}
-
-// MethodHandler describes the functionality of *all* possible DID service, regardless of method
-type MethodHandler interface {
-	CreateDID(ctx context.Context, request CreateDIDRequest) (*CreateDIDResponse, error)
-	GetDID(ctx context.Context, request GetDIDRequest) (*GetDIDResponse, error)
-	GetDIDs(ctx context.Context, method didsdk.Method) (*GetDIDsResponse, error)
-	SoftDeleteDID(ctx context.Context, request DeleteDIDRequest) error
-}
-
 func (s *Service) instantiateHandlerForMethod(method didsdk.Method) error {
 	switch method {
 	case didsdk.KeyMethod:
@@ -152,17 +132,8 @@ func (s *Service) ResolveDID(request ResolveDIDRequest) (*ResolveDIDResponse, er
 	}, nil
 }
 
-func (s *Service) Resolve(ctx context.Context, did string, _ ...didsdk.ResolutionOptions) (*didsdk.ResolutionResult, error) {
-	selectedResolver, err := s.chooseResolver(did)
-	if err != nil {
-		return nil, util.LoggingErrorMsg(err, "choosing resolver")
-	}
-
-	resolved, err := selectedResolver.Resolve(ctx, did)
-	if err != nil {
-		return nil, util.LoggingErrorMsgf(err, "could not resolve DID: %s", did)
-	}
-	return resolved, nil
+func (s *Service) Resolve(ctx context.Context, did string, opts ...didsdk.ResolutionOption) (*didsdk.ResolutionResult, error) {
+	return s.resolver.Resolve(ctx, did, opts)
 }
 
 func (s *Service) GetSupportedMethods() GetSupportedMethodsResponse {
@@ -189,6 +160,24 @@ func (s *Service) GetDIDByMethod(ctx context.Context, request GetDIDRequest) (*G
 	return handler.GetDID(ctx, request)
 }
 
+func (s *Service) GetKeyFromDID(ctx context.Context, request GetKeyFromDIDRequest) (*GetKeyFromDIDResponse, error) {
+	resolved, err := s.Resolve(ctx, request.ID)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "resolving DID<%s>", request.ID)
+	}
+
+	// next, get the verification information (key) from the did document
+	kid, pubKey, err := did.GetVerificationInformation(resolved.Document, request.KeyID)
+	if err != nil {
+		return nil, errors.Wrapf(err, "getting verification information from the did document: %s", request.ID)
+	}
+
+	return &GetKeyFromDIDResponse{
+		KeyID:     kid,
+		PublicKey: pubKey,
+	}, nil
+}
+
 func (s *Service) GetDIDsByMethod(ctx context.Context, request GetDIDsRequest) (*GetDIDsResponse, error) {
 	method := request.Method
 	handler, err := s.getHandler(method)
@@ -213,17 +202,3 @@ func (s *Service) getHandler(method didsdk.Method) (MethodHandler, error) {
 	}
 	return handler, nil
 }
-
-func (s *Service) chooseResolver(did string) (resolve.Resolver, error) {
-	didMethod, err := util.GetMethodForDID(did)
-	if err != nil {
-		return nil, errors.Wrap(err, "getting method for did")
-	}
-
-	r, ok := s.methodToResolver[didMethod]
-	if !ok {
-		return nil, errors.Errorf("resolver for %s not available", didMethod)
-	}
-
-	return r, nil
-}
diff --git a/pkg/service/did/storage.go b/pkg/service/did/storage.go
index 84892d0..283ee50 100644
--- a/pkg/service/did/storage.go
+++ b/pkg/service/did/storage.go
@@ -119,7 +119,7 @@ func getNamespaceForDID(id string) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	ns, err := getNamespaceForMethod(method)
+	ns, err := getNamespaceForMethod(method.String())
 	if err != nil {
 		return "", err
 	}
diff --git a/pkg/service/did/web.go b/pkg/service/did/web.go
index 64aa356..173f436 100644
--- a/pkg/service/did/web.go
+++ b/pkg/service/did/web.go
@@ -103,7 +103,6 @@ func (h *webDIDHandler) GetDID(ctx context.Context, request GetDIDRequest) (*Get
 }
 
 func (h *webDIDHandler) GetDIDs(ctx context.Context, method did.Method) (*GetDIDsResponse, error) {
-
 	logrus.Debugf("getting DIDs for method: %s", method)
 
 	gotDIDs, err := h.storage.GetDIDs(ctx, string(method))
diff --git a/pkg/service/manifest/application.go b/pkg/service/manifest/application.go
index 39ab941..066856a 100644
--- a/pkg/service/manifest/application.go
+++ b/pkg/service/manifest/application.go
@@ -5,26 +5,22 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/tbd54566975/ssi-service/pkg/jwt"
+	didint "github.com/tbd54566975/ssi-service/internal/did"
 	"github.com/tbd54566975/ssi-service/pkg/service/manifest/model"
 
 	"github.com/TBD54566975/ssi-sdk/credential/manifest"
 	errresp "github.com/TBD54566975/ssi-sdk/error"
-	"github.com/tbd54566975/ssi-service/internal/keyaccess"
+
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/service/credential"
 )
 
-func (s Service) verifyApplicationJWT(ctx context.Context, did string, token keyaccess.JWT) error {
-	return jwt.VerifyTokenFromDID(ctx, did, token, s.didResolver)
-}
-
 // validateCredentialApplication validates the credential application's signature(s) in addition to making sure it
 // is a valid credential application, and complies with its corresponding manifest. it returns the ids of unfulfilled
 // input descriptors along with an error if validation fails.
 func (s Service) validateCredentialApplication(ctx context.Context, credManifest manifest.CredentialManifest, request model.SubmitApplicationRequest) (inputDescriptorIDs []string, err error) {
 	// validate the payload's signature
-	if verificationErr := s.verifyApplicationJWT(ctx, request.ApplicantDID, request.ApplicationJWT); verificationErr != nil {
+	if verificationErr := didint.VerifyTokenFromDID(ctx, s.didResolver, request.ApplicantDID, request.ApplicationJWT); verificationErr != nil {
 		err = util.LoggingErrorMsgf(err, "could not verify application<%s>'s signature", request.Application.ID)
 		return
 	}
diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go
index 1ba36fd..44af41a 100644
--- a/pkg/service/manifest/manifest.go
+++ b/pkg/service/manifest/manifest.go
@@ -37,26 +37,20 @@ func (s Service) verifyManifestJWT(ctx context.Context, token keyaccess.JWT) (*m
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not parse JWT")
 	}
+
 	claims := parsed.PrivateClaims()
 	claimsJSONBytes, err := json.Marshal(claims)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not marshal claims")
 	}
+
 	var parsedManifest CredentialManifestContainer
 	if err = json.Unmarshal(claimsJSONBytes, &parsedManifest); err != nil {
-		return nil, util.LoggingErrorMsg(err, "could not unmarshal claims into manifest")
-	}
-	issuer := parsedManifest.Manifest.Issuer.ID
-	kid, pubKey, err := didint.ResolveKeyForDID(ctx, s.didResolver, issuer)
-	if err != nil {
-		return nil, util.LoggingErrorMsgf(err, "failed to resolve manifest issuer's did: %s", issuer)
+		return nil, util.LoggingErrorMsg(err, "unmarshalling claims into manifest")
 	}
-	verifier, err := keyaccess.NewJWKKeyAccessVerifier(kid, pubKey)
-	if err != nil {
-		return nil, util.LoggingErrorMsg(err, "could not create manifest verifier")
-	}
-	if err = verifier.Verify(token); err != nil {
-		return nil, util.LoggingErrorMsg(err, "could not verify the manifest's signature")
+
+	if err = didint.VerifyTokenFromDID(ctx, s.didResolver, parsedManifest.Manifest.Issuer.ID, token); err != nil {
+		return nil, util.LoggingErrorMsg(err, "verifying manifest JWT")
 	}
 	return &parsedManifest.Manifest, nil
 }
diff --git a/pkg/service/manifest/service.go b/pkg/service/manifest/service.go
index 090827f..cccca36 100644
--- a/pkg/service/manifest/service.go
+++ b/pkg/service/manifest/service.go
@@ -5,17 +5,18 @@ import (
 	"fmt"
 
 	"github.com/TBD54566975/ssi-sdk/credential/manifest"
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	errresp "github.com/TBD54566975/ssi-sdk/error"
 	sdkutil "github.com/TBD54566975/ssi-sdk/util"
 	"github.com/benbjohnson/clock"
 	"github.com/goccy/go-json"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+
 	"github.com/tbd54566975/ssi-service/config"
 	credint "github.com/tbd54566975/ssi-service/internal/credential"
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/service/credential"
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
 	"github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/issuing"
 	"github.com/tbd54566975/ssi-service/pkg/service/keystore"
@@ -35,7 +36,7 @@ type Service struct {
 
 	// external dependencies
 	keyStore    *keystore.Service
-	didResolver resolve.Resolver
+	didResolver didsdk.Resolver
 	credential  *credential.Service
 
 	Clock clock.Clock
@@ -72,7 +73,7 @@ func (s Service) Config() config.ManifestServiceConfig {
 	return s.config
 }
 
-func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service, didResolver resolve.Resolver, credential *credential.Service) (*Service, error) {
+func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service, didResolver didsdk.Resolver, credential *credential.Service) (*Service, error) {
 	manifestStorage, err := manifeststg.NewManifestStorage(s)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not instantiate storage for the manifest service")
@@ -103,7 +104,6 @@ type CredentialManifestContainer struct {
 }
 
 func (s Service) CreateManifest(ctx context.Context, request model.CreateManifestRequest) (*model.CreateManifestResponse, error) {
-
 	logrus.Debugf("creating manifest: %+v", request)
 
 	// validate the request
@@ -199,14 +199,13 @@ func (s Service) VerifyManifest(ctx context.Context, request model.VerifyManifes
 	}
 
 	// check the manifest is valid against its specification
-	if err := m.IsValid(); err != nil {
+	if err = m.IsValid(); err != nil {
 		return &model.VerifyManifestResponse{Verified: false, Reason: "manifest is not valid: " + err.Error()}, nil
 	}
 	return &model.VerifyManifestResponse{Verified: true}, nil
 }
 
 func (s Service) GetManifest(ctx context.Context, request model.GetManifestRequest) (*model.GetManifestResponse, error) {
-
 	logrus.Debugf("getting manifest: %s", request.ID)
 
 	gotManifest, err := s.storage.GetManifest(ctx, request.ID)
@@ -235,7 +234,6 @@ func (s Service) GetManifests(ctx context.Context) (*model.GetManifestsResponse,
 }
 
 func (s Service) DeleteManifest(ctx context.Context, request model.DeleteManifestRequest) error {
-
 	logrus.Debugf("deleting manifest: %s", request.ID)
 
 	if err := s.storage.DeleteManifest(ctx, request.ID); err != nil {
@@ -518,7 +516,6 @@ func (s Service) GetApplications(ctx context.Context) (*model.GetApplicationsRes
 }
 
 func (s Service) DeleteApplication(ctx context.Context, request model.DeleteApplicationRequest) error {
-
 	logrus.Debugf("deleting application: %s", request.ID)
 
 	if err := s.storage.DeleteApplication(ctx, request.ID); err != nil {
@@ -529,7 +526,6 @@ func (s Service) DeleteApplication(ctx context.Context, request model.DeleteAppl
 }
 
 func (s Service) GetResponse(ctx context.Context, request model.GetResponseRequest) (*model.GetResponseResponse, error) {
-
 	logrus.Debugf("getting response: %s", request.ID)
 
 	gotResponse, err := s.storage.GetResponse(ctx, request.ID)
@@ -546,7 +542,6 @@ func (s Service) GetResponse(ctx context.Context, request model.GetResponseReque
 }
 
 func (s Service) GetResponses(ctx context.Context) (*model.GetResponsesResponse, error) {
-
 	logrus.Debugf("getting response(s)")
 
 	gotResponses, err := s.storage.GetResponses(ctx)
@@ -564,7 +559,6 @@ func (s Service) GetResponses(ctx context.Context) (*model.GetResponsesResponse,
 }
 
 func (s Service) DeleteResponse(ctx context.Context, request model.DeleteResponseRequest) error {
-
 	logrus.Debugf("deleting response: %s", request.ID)
 
 	if err := s.storage.DeleteResponse(ctx, request.ID); err != nil {
diff --git a/pkg/service/presentation/service.go b/pkg/service/presentation/service.go
index 85cf828..c94eb68 100644
--- a/pkg/service/presentation/service.go
+++ b/pkg/service/presentation/service.go
@@ -6,14 +6,15 @@ import (
 
 	"github.com/TBD54566975/ssi-sdk/credential/exchange"
 	"github.com/TBD54566975/ssi-sdk/credential/signing"
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	sdkutil "github.com/TBD54566975/ssi-sdk/util"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+
 	"github.com/tbd54566975/ssi-service/config"
 	"github.com/tbd54566975/ssi-service/internal/credential"
+	didint "github.com/tbd54566975/ssi-service/internal/did"
 	"github.com/tbd54566975/ssi-service/internal/util"
-	"github.com/tbd54566975/ssi-service/pkg/jwt"
-	"github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
 	"github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/keystore"
 	"github.com/tbd54566975/ssi-service/pkg/service/operation"
@@ -30,7 +31,7 @@ type Service struct {
 	keystore   *keystore.Service
 	opsStorage *operation.Storage
 	config     config.PresentationServiceConfig
-	resolver   resolve.Resolver
+	resolver   didsdk.Resolver
 	schema     *schema.Service
 	verifier   *credential.Verifier
 }
@@ -57,7 +58,7 @@ func (s Service) Config() config.PresentationServiceConfig {
 	return s.config
 }
 
-func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage, resolver resolve.Resolver, schema *schema.Service, keystore *keystore.Service) (*Service, error) {
+func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage, resolver didsdk.Resolver, schema *schema.Service, keystore *keystore.Service) (*Service, error) {
 	presentationStorage, err := NewPresentationStorage(s)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not instantiate definition storage for the presentation service")
@@ -164,11 +165,13 @@ func (s Service) CreateSubmission(ctx context.Context, request model.CreateSubmi
 	if err != nil {
 		return nil, errors.Wrap(err, "parsing vp from jwt")
 	}
-	if err := jwt.VerifyTokenFromDID(ctx, sdkVP.Holder, request.SubmissionJWT, s.resolver); err != nil {
+
+	// verify the token with the did by first resolving the did and getting the public key and next verifying the token
+	if err = didint.VerifyTokenFromDID(ctx, s.resolver, sdkVP.Holder, request.SubmissionJWT); err != nil {
 		return nil, errors.Wrap(err, "verifying token from did")
 	}
 
-	if _, err := s.storage.GetSubmission(ctx, request.Submission.ID); !errors.Is(err, presentationstorage.ErrSubmissionNotFound) {
+	if _, err = s.storage.GetSubmission(ctx, request.Submission.ID); !errors.Is(err, presentationstorage.ErrSubmissionNotFound) {
 		return nil, errors.Errorf("submission with id %s already present", request.Submission.ID)
 	}
 
@@ -182,19 +185,19 @@ func (s Service) CreateSubmission(ctx context.Context, request model.CreateSubmi
 			return nil, errors.Errorf("invalid credential %+v", cred)
 		}
 		if cred.CredentialJWT != nil {
-			if err := s.verifier.VerifyJWTCredential(ctx, *cred.CredentialJWT); err != nil {
+			if err = s.verifier.VerifyJWTCredential(ctx, *cred.CredentialJWT); err != nil {
 				return nil, errors.Wrapf(err, "verifying jwt credential %s", cred.CredentialJWT)
 			}
 		} else {
 			if cred.HasDataIntegrityCredential() {
-				if err := s.verifier.VerifyDataIntegrityCredential(ctx, *cred.Credential); err != nil {
+				if err = s.verifier.VerifyDataIntegrityCredential(ctx, *cred.Credential); err != nil {
 					return nil, errors.Wrapf(err, "verifying data integrity credential %+v", cred.Credential)
 				}
 			}
 		}
 	}
 
-	if err := exchange.VerifyPresentationSubmissionVP(definition.PresentationDefinition, request.Presentation); err != nil {
+	if err = exchange.VerifyPresentationSubmissionVP(definition.PresentationDefinition, request.Presentation); err != nil {
 		return nil, errors.Wrap(err, "verifying presentation submission vp")
 	}
 
@@ -204,7 +207,7 @@ func (s Service) CreateSubmission(ctx context.Context, request model.CreateSubmi
 	}
 
 	// TODO(andres): IO requests should be done in parallel, once we have context wired up.
-	if err := s.storage.StoreSubmission(ctx, storedSubmission); err != nil {
+	if err = s.storage.StoreSubmission(ctx, storedSubmission); err != nil {
 		return nil, errors.Wrap(err, "could not store presentation")
 	}
 
@@ -217,7 +220,7 @@ func (s Service) CreateSubmission(ctx context.Context, request model.CreateSubmi
 		ID:   opID,
 		Done: false,
 	}
-	if err := s.opsStorage.StoreOperation(ctx, storedOp); err != nil {
+	if err = s.opsStorage.StoreOperation(ctx, storedOp); err != nil {
 		return nil, errors.Wrap(err, "could not store operation")
 	}
 
diff --git a/pkg/service/schema/service.go b/pkg/service/schema/service.go
index bdea8f4..7c84745 100644
--- a/pkg/service/schema/service.go
+++ b/pkg/service/schema/service.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/TBD54566975/ssi-sdk/credential/schema"
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	schemalib "github.com/TBD54566975/ssi-sdk/schema"
 	sdkutil "github.com/TBD54566975/ssi-sdk/util"
 	"github.com/goccy/go-json"
@@ -14,8 +15,6 @@ import (
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 
-	didresolver "github.com/tbd54566975/ssi-service/pkg/service/did/resolve"
-
 	"github.com/tbd54566975/ssi-service/config"
 	"github.com/tbd54566975/ssi-service/internal/did"
 	"github.com/tbd54566975/ssi-service/internal/keyaccess"
@@ -32,7 +31,7 @@ type Service struct {
 
 	// external dependencies
 	keyStore *keystore.Service
-	resolver didresolver.Resolver
+	resolver didsdk.Resolver
 }
 
 func (s Service) Type() framework.Type {
@@ -63,7 +62,7 @@ func (s Service) Config() config.SchemaServiceConfig {
 	return s.config
 }
 
-func NewSchemaService(config config.SchemaServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service, resolver didresolver.Resolver) (*Service, error) {
+func NewSchemaService(config config.SchemaServiceConfig, s storage.ServiceStorage, keyStore *keystore.Service, resolver didsdk.Resolver) (*Service, error) {
 	schemaStorage, err := NewSchemaStorage(s)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not instantiate storage for the schema service")


Test patch:
diff --git a/internal/did/resolver_test.go b/internal/did/resolver_test.go
index 4f6c4a4..520aed2 100644
--- a/internal/did/resolver_test.go
+++ b/internal/did/resolver_test.go
@@ -1,6 +1,7 @@
 package did
 
 import (
+	"context"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -8,25 +9,25 @@ import (
 
 func TestResolver(t *testing.T) {
 	// empty resolver
-	_, err := BuildResolver(nil)
+	_, err := BuildMultiMethodResolver(nil)
 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "no methods provided")
 
 	// unsupported method
-	_, err = BuildResolver([]string{"unsupported"})
+	_, err = BuildMultiMethodResolver([]string{"unsupported"})
 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "unsupported method: unsupported")
 
 	// valid method
-	resolver, err := BuildResolver([]string{"key"})
+	resolver, err := BuildMultiMethodResolver([]string{"key"})
 	assert.NoError(t, err)
 	assert.NotEmpty(t, resolver)
-	resolved, err := resolver.Resolve("did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp")
+	resolved, err := resolver.Resolve(context.Background(), "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp")
 	assert.NoError(t, err)
 	assert.NotEmpty(t, resolved)
 
-	// resolve for a method that is not supported
-	_, err = resolver.Resolve("did:web:example.com")
+	// resolution for a method that is not supported
+	_, err = resolver.Resolve(context.Background(), "did:web:example.com")
 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "unsupported method: web")
 }
diff --git a/pkg/server/server_did_test.go b/pkg/server/server_did_test.go
index 808d3da..0f14979 100644
--- a/pkg/server/server_did_test.go
+++ b/pkg/server/server_did_test.go
@@ -353,7 +353,7 @@ func TestDIDAPI(t *testing.T) {
 		}
 		err := didService.ResolveDID(newRequestContextWithParams(badParams), w, req)
 		assert.Error(tt, err)
-		assert.Contains(tt, err.Error(), "malformed did: bad")
+		assert.Contains(tt, err.Error(), "malformed did")
 
 		w.Flush()
 
@@ -364,7 +364,7 @@ func TestDIDAPI(t *testing.T) {
 		}
 		err = didService.ResolveDID(newRequestContextWithParams(badParams), w, req)
 		assert.Error(tt, err)
-		assert.Contains(tt, err.Error(), "selected encoding not supported")
+		assert.Contains(tt, err.Error(), "unable to resolve DID did:key:abcd")
 
 		w.Flush()
 
diff --git a/pkg/service/did/resolution/universal_test.go b/pkg/service/did/resolution/universal_test.go
new file mode 100644
index 0000000..fc92f4f
--- /dev/null
+++ b/pkg/service/did/resolution/universal_test.go
@@ -0,0 +1,35 @@
+package resolution
+
+import (
+	"context"
+	"testing"
+
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
+	"github.com/stretchr/testify/assert"
+)
+
+// TestUniversalResolver tests the universal resolver's dev instance. It is intentionally skipped to not run in CI.
+func TestUniversalResolver(t *testing.T) {
+	t.Skip("skipping integration test")
+	t.Run("test get methods", func(tt *testing.T) {
+		resolver, err := newUniversalResolver("https://dev.uniresolver.io")
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, resolver)
+
+		methods := resolver.Methods()
+		assert.NotEmpty(tt, methods)
+		assert.Contains(tt, methods, didsdk.Method("ion"))
+	})
+
+	t.Run("test get ion resolution", func(tt *testing.T) {
+		resolver, err := newUniversalResolver("https://dev.uniresolver.io")
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, resolver)
+
+		resolution, err := resolver.Resolve(context.Background(), "did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w")
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, resolution)
+
+		assert.Equal(tt, "did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w", resolution.Document.ID)
+	})
+}