From 512aee2a50def5720c3dc12bd96443c63c406446 Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Thu, 24 Jul 2025 18:49:11 -0400 Subject: [PATCH 1/6] Add liceneses and links for web-did-resolver --- .../did/licenses/web-did-resolver.LICENSE | 201 ++++++++++++++++++ .../did/src/did-resolvers/web-did-resolver.ts | 3 + 2 files changed, 204 insertions(+) create mode 100644 packages/did/licenses/web-did-resolver.LICENSE diff --git a/packages/did/licenses/web-did-resolver.LICENSE b/packages/did/licenses/web-did-resolver.LICENSE new file mode 100644 index 0000000..fd815d7 --- /dev/null +++ b/packages/did/licenses/web-did-resolver.LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Consensys AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/did/src/did-resolvers/web-did-resolver.ts b/packages/did/src/did-resolvers/web-did-resolver.ts index bd4d3e7..77f17d7 100644 --- a/packages/did/src/did-resolvers/web-did-resolver.ts +++ b/packages/did/src/did-resolvers/web-did-resolver.ts @@ -5,6 +5,9 @@ * * The error messages should match exactly with the `web` resolver from the * `web-did-resolver` package. + * + * @see {@link https://github.com/decentralized-identity/web-did-resolver} + * @see {@link ../../licenses/web-did-resolver.LICENSE} */ import { isDidDocument, isDidDocumentForDid } from "../did-document" import { isDidWebUri } from "../methods/did-web" From 72eecbac23840f81758a84d38d715b4aa7d57883 Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Thu, 24 Jul 2025 18:51:55 -0400 Subject: [PATCH 2/6] Add schemas for CAIP-2, CAIP-10, CAIP-19 --- .changeset/poor-tips-judge.md | 11 + demos/e2e/package.json | 2 +- demos/e2e/src/agent.ts | 8 +- demos/e2e/src/user.ts | 8 +- demos/identity-a2a/package.json | 2 +- demos/identity/package.json | 2 +- demos/payments/package.json | 2 +- demos/payments/src/receipt-service.ts | 4 +- demos/skyfire-kya/package.json | 2 +- examples/issuer/package.json | 2 +- examples/local-did-host/package.json | 2 +- examples/verifier/package.json | 2 +- packages/ack-id/package.json | 2 +- packages/ack-id/src/a2a/schemas/zod/v3.ts | 2 +- packages/ack-id/src/schemas/zod/v3.ts | 2 +- packages/ack-pay/package.json | 2 +- packages/ack-pay/src/schemas/zod/v3.ts | 2 +- packages/agentcommercekit/package.json | 2 +- packages/did/package.json | 3 +- packages/did/src/caip/caip.test.ts | 33 +++ packages/did/src/caip/caip.ts | 16 ++ packages/did/src/caip/index.ts | 2 + packages/did/src/caip/schemas.test.ts | 193 +++++++++++++++ packages/did/src/caip/schemas/core.ts | 105 ++++++++ packages/did/src/caip/schemas/valibot.ts | 48 ++++ packages/did/src/caip/schemas/zod/v3.ts | 28 +++ packages/did/src/caip/schemas/zod/v4.ts | 28 +++ packages/did/src/caip/types.ts | 7 + packages/did/src/did-uri.ts | 5 +- packages/did/src/index.ts | 1 + packages/did/src/methods/did-pkh.test.ts | 284 ++++++++++++++++++++++ packages/did/src/methods/did-pkh.ts | 34 ++- packages/did/src/schemas/valibot.ts | 12 +- packages/did/src/schemas/zod/v3.ts | 13 +- packages/did/src/schemas/zod/v4.ts | 11 +- packages/did/vitest.config.ts | 1 + packages/did/vitest.setup.ts | 13 + packages/jwt/package.json | 2 +- packages/jwt/src/schemas/zod/v3.ts | 2 +- packages/keys/package.json | 2 +- packages/vc/package.json | 2 +- packages/vc/src/schemas/zod/v3.ts | 2 +- pnpm-lock.yaml | 251 ++++++++++++------- tools/api-utils/package.json | 2 +- tools/cli-tools/package.json | 2 +- 45 files changed, 1007 insertions(+), 154 deletions(-) create mode 100644 .changeset/poor-tips-judge.md create mode 100644 packages/did/src/caip/caip.test.ts create mode 100644 packages/did/src/caip/caip.ts create mode 100644 packages/did/src/caip/index.ts create mode 100644 packages/did/src/caip/schemas.test.ts create mode 100644 packages/did/src/caip/schemas/core.ts create mode 100644 packages/did/src/caip/schemas/valibot.ts create mode 100644 packages/did/src/caip/schemas/zod/v3.ts create mode 100644 packages/did/src/caip/schemas/zod/v4.ts create mode 100644 packages/did/src/caip/types.ts create mode 100644 packages/did/src/methods/did-pkh.test.ts create mode 100644 packages/did/vitest.setup.ts diff --git a/.changeset/poor-tips-judge.md b/.changeset/poor-tips-judge.md new file mode 100644 index 0000000..7f78870 --- /dev/null +++ b/.changeset/poor-tips-judge.md @@ -0,0 +1,11 @@ +--- +"agentcommercekit": patch +"@agentcommercekit/ack-pay": patch +"@agentcommercekit/ack-id": patch +"@agentcommercekit/keys": patch +"@agentcommercekit/did": patch +"@agentcommercekit/jwt": patch +"@agentcommercekit/vc": patch +--- + +Add schemas for CAIP-2, CAIP-10, CAIP-19 which are used by did:pkh diff --git a/demos/e2e/package.json b/demos/e2e/package.json index 2ffca5c..bc480e6 100644 --- a/demos/e2e/package.json +++ b/demos/e2e/package.json @@ -36,6 +36,6 @@ "eslint": "^9.27.0", "tsx": "^4.19.4", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/demos/e2e/src/agent.ts b/demos/e2e/src/agent.ts index 4103a12..6082a10 100644 --- a/demos/e2e/src/agent.ts +++ b/demos/e2e/src/agent.ts @@ -13,8 +13,8 @@ import { verifyAgentIdentityWithCredential } from "./verification" import type { CredentialVerifier } from "./credential-verifier" import type { ReceiptVerifier } from "./receipt-verifier" import type { + Caip2ChainId, DidDocument, - DidPkhChainId, DidResolver, DidUri, JwtSigner, @@ -32,7 +32,7 @@ interface AgentConstructorParams { receiptVerifier: ReceiptVerifier credentialVerifier: CredentialVerifier wallet: Keypair - preferredChainId: DidPkhChainId + preferredChainId: Caip2ChainId } export class Agent { @@ -48,7 +48,7 @@ export class Agent { private readonly resolver: DidResolver private readonly receiptVerifier: ReceiptVerifier private readonly credentialVerifier: CredentialVerifier - private readonly preferredChainId: DidPkhChainId + private readonly preferredChainId: Caip2ChainId // An optional map of id to the request itself (e.g. product, message, etc) private pendingRequests: Record = {} @@ -105,7 +105,7 @@ export class Agent { credentialVerifier }: { ownerDid: DidUri - preferredChainId: DidPkhChainId + preferredChainId: Caip2ChainId resolver: DidResolver receiptVerifier: ReceiptVerifier credentialVerifier: CredentialVerifier diff --git a/demos/e2e/src/user.ts b/demos/e2e/src/user.ts index b746227..985e15e 100644 --- a/demos/e2e/src/user.ts +++ b/demos/e2e/src/user.ts @@ -5,8 +5,8 @@ import { } from "agentcommercekit" import { publicKeyToAddress } from "./utils/evm-address" import type { + Caip2ChainId, DidDocument, - DidPkhChainId, DidResolver, DidUri, JwtSigner, @@ -15,7 +15,7 @@ import type { interface ConstructorParams { wallet: Keypair - preferredChainId: DidPkhChainId + preferredChainId: Caip2ChainId did: DidUri didDocument: DidDocument resolver: DidResolver @@ -23,7 +23,7 @@ interface ConstructorParams { export class User { readonly wallet: Keypair - readonly preferredChainId: DidPkhChainId + readonly preferredChainId: Caip2ChainId readonly did: DidUri readonly didDocument: DidDocument readonly signer: JwtSigner @@ -48,7 +48,7 @@ export class User { static async create( resolver: DidResolver, - chainId: DidPkhChainId + chainId: Caip2ChainId ): Promise { const wallet = await generateKeypair("secp256k1") const address = publicKeyToAddress(wallet.publicKey) diff --git a/demos/identity-a2a/package.json b/demos/identity-a2a/package.json index df77767..06b8b1b 100644 --- a/demos/identity-a2a/package.json +++ b/demos/identity-a2a/package.json @@ -41,6 +41,6 @@ "eslint": "^9.27.0", "tsx": "^4.19.4", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/demos/identity/package.json b/demos/identity/package.json index 76fb3bb..4eafb2c 100644 --- a/demos/identity/package.json +++ b/demos/identity/package.json @@ -46,6 +46,6 @@ "eslint": "^9.27.0", "tsx": "^4.19.4", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/demos/payments/package.json b/demos/payments/package.json index 1d67d60..55340b7 100644 --- a/demos/payments/package.json +++ b/demos/payments/package.json @@ -42,6 +42,6 @@ "eslint": "^9.27.0", "tsx": "^4.19.4", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/demos/payments/src/receipt-service.ts b/demos/payments/src/receipt-service.ts index 76c1d53..f221be1 100644 --- a/demos/payments/src/receipt-service.ts +++ b/demos/payments/src/receipt-service.ts @@ -15,7 +15,7 @@ import { verifyJwt, verifyPaymentToken } from "agentcommercekit" -import { didPkhChainIdSchema } from "agentcommercekit/schemas/valibot" +import { caip2ChainIdSchema } from "agentcommercekit/schemas/valibot" import { Hono } from "hono" import { env } from "hono/adapter" import { HTTPException } from "hono/http-exception" @@ -38,7 +38,7 @@ const bodySchema = v.object({ const paymentDetailsSchema = v.object({ metadata: v.union([ v.object({ - network: didPkhChainIdSchema, + network: caip2ChainIdSchema, txHash: v.string() }), v.object({ diff --git a/demos/skyfire-kya/package.json b/demos/skyfire-kya/package.json index 40b9cdb..f0b98dd 100644 --- a/demos/skyfire-kya/package.json +++ b/demos/skyfire-kya/package.json @@ -35,6 +35,6 @@ "eslint": "^9.27.0", "tsx": "^4.19.4", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/examples/issuer/package.json b/examples/issuer/package.json index f35ec10..d603d1d 100644 --- a/examples/issuer/package.json +++ b/examples/issuer/package.json @@ -46,6 +46,6 @@ "eslint": "^9.27.0", "tsx": "^4.19.4", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/examples/local-did-host/package.json b/examples/local-did-host/package.json index 19d7b03..3a5e761 100644 --- a/examples/local-did-host/package.json +++ b/examples/local-did-host/package.json @@ -41,6 +41,6 @@ "dotenv-cli": "^8.0.0", "eslint": "^9.27.0", "tsx": "^4.19.4", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/examples/verifier/package.json b/examples/verifier/package.json index 7e983e0..dbd5430 100644 --- a/examples/verifier/package.json +++ b/examples/verifier/package.json @@ -39,6 +39,6 @@ "dotenv-cli": "^8.0.0", "eslint": "^9.27.0", "tsx": "^4.19.4", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/packages/ack-id/package.json b/packages/ack-id/package.json index d6794bb..ca1ff5f 100644 --- a/packages/ack-id/package.json +++ b/packages/ack-id/package.json @@ -90,7 +90,7 @@ "eslint": "^9.27.0", "tsdown": "^0.11.12", "typescript": "^5", - "vitest": "^3.1.4", + "vitest": "^3.2.4", "zod": "^3.25.0" }, "peerDependencies": { diff --git a/packages/ack-id/src/a2a/schemas/zod/v3.ts b/packages/ack-id/src/a2a/schemas/zod/v3.ts index 9039273..2b3a8c5 100644 --- a/packages/ack-id/src/a2a/schemas/zod/v3.ts +++ b/packages/ack-id/src/a2a/schemas/zod/v3.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import { z } from "zod/v3" const roleSchema = z.enum(["agent", "user"]) diff --git a/packages/ack-id/src/schemas/zod/v3.ts b/packages/ack-id/src/schemas/zod/v3.ts index 0fbd4d7..e155fa9 100644 --- a/packages/ack-id/src/schemas/zod/v3.ts +++ b/packages/ack-id/src/schemas/zod/v3.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import { z } from "zod/v3" export const controllerClaimSchema = z.object({ id: z.string(), diff --git a/packages/ack-pay/package.json b/packages/ack-pay/package.json index 288a697..ce2338a 100644 --- a/packages/ack-pay/package.json +++ b/packages/ack-pay/package.json @@ -67,7 +67,7 @@ "eslint": "^9.27.0", "tsdown": "^0.11.12", "typescript": "^5", - "vitest": "^3.1.4", + "vitest": "^3.2.4", "zod": "^3.25.0" }, "peerDependencies": { diff --git a/packages/ack-pay/src/schemas/zod/v3.ts b/packages/ack-pay/src/schemas/zod/v3.ts index 1f643b0..9f572fb 100644 --- a/packages/ack-pay/src/schemas/zod/v3.ts +++ b/packages/ack-pay/src/schemas/zod/v3.ts @@ -1,5 +1,5 @@ import { didUriSchema } from "@agentcommercekit/did/schemas/zod/v3" -import { z } from "zod" +import { z } from "zod/v3" const urlOrDidUri = z.union([z.string().url(), didUriSchema]) diff --git a/packages/agentcommercekit/package.json b/packages/agentcommercekit/package.json index 7125499..957badb 100644 --- a/packages/agentcommercekit/package.json +++ b/packages/agentcommercekit/package.json @@ -90,7 +90,7 @@ "tsdown": "^0.11.12", "typescript": "^5", "valibot": "^1.1.0", - "vitest": "^3.1.4", + "vitest": "^3.2.4", "zod": "^3.25.0" }, "peerDependencies": { diff --git a/packages/did/package.json b/packages/did/package.json index f8cd6cb..3a8954a 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -67,9 +67,10 @@ "@repo/typescript-config": "workspace:*", "@types/varint": "^6.0.3", "eslint": "^9.27.0", + "standard-parse": "^0.3.0", "tsdown": "^0.11.12", "typescript": "^5", - "vitest": "^3.1.4", + "vitest": "^3.2.4", "zod": "^3.25.0" }, "peerDependencies": { diff --git a/packages/did/src/caip/caip.test.ts b/packages/did/src/caip/caip.test.ts new file mode 100644 index 0000000..4b95ccb --- /dev/null +++ b/packages/did/src/caip/caip.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest" +import { isCaip2ChainId } from "./caip" + +describe("isCaip2ChainId", () => { + it("returns true for valid CAIP-2 chain IDs", () => { + expect(isCaip2ChainId("eip155:1")).toBe(true) + expect(isCaip2ChainId("eip155:11155111")).toBe(true) + expect(isCaip2ChainId("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")).toBe(true) + expect(isCaip2ChainId("bitcoin:mainnet")).toBe(true) + expect(isCaip2ChainId("cosmos:cosmoshub-4")).toBe(true) + }) + + it("returns false for invalid chain IDs", () => { + expect(isCaip2ChainId("invalid")).toBe(false) + expect(isCaip2ChainId("eip155")).toBe(false) + expect(isCaip2ChainId(":1")).toBe(false) + expect(isCaip2ChainId("eip155:")).toBe(false) + expect(isCaip2ChainId("")).toBe(false) + expect(isCaip2ChainId(null)).toBe(false) + expect(isCaip2ChainId(undefined)).toBe(false) + expect(isCaip2ChainId(123)).toBe(false) + }) + + it("returns false for chain IDs with invalid namespace length", () => { + expect(isCaip2ChainId("ab:1")).toBe(false) // too short + expect(isCaip2ChainId("verylongnamespace:1")).toBe(false) // too long + }) + + it("returns false for chain IDs with invalid characters", () => { + expect(isCaip2ChainId("EIP155:1")).toBe(false) // uppercase not allowed in namespace + expect(isCaip2ChainId("eip-155:1")).toBe(false) // hyphen not allowed in namespace + }) +}) diff --git a/packages/did/src/caip/caip.ts b/packages/did/src/caip/caip.ts new file mode 100644 index 0000000..ede88b3 --- /dev/null +++ b/packages/did/src/caip/caip.ts @@ -0,0 +1,16 @@ +import * as v from "valibot" +import { caip2ChainIdSchema } from "./schemas/valibot" +import type { Caip2ChainId } from "./types" + +/** + * Checks if a given string is a valid CAIP-2 chain ID (`namespace:reference`) + * chain_id: namespace + ":" + reference + * namespace: [-a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + * + * @param chainId - The chain ID to check + * @returns `true` if the chain ID is a valid CAIP-2 chain ID, `false` otherwise + */ +export function isCaip2ChainId(chainId: unknown): chainId is Caip2ChainId { + return v.is(caip2ChainIdSchema, chainId) +} diff --git a/packages/did/src/caip/index.ts b/packages/did/src/caip/index.ts new file mode 100644 index 0000000..399fc3b --- /dev/null +++ b/packages/did/src/caip/index.ts @@ -0,0 +1,2 @@ +export * from "./caip" +export type * from "./types" diff --git a/packages/did/src/caip/schemas.test.ts b/packages/did/src/caip/schemas.test.ts new file mode 100644 index 0000000..b429085 --- /dev/null +++ b/packages/did/src/caip/schemas.test.ts @@ -0,0 +1,193 @@ +import { describe, expect, it } from "vitest" +import * as valibot from "./schemas/valibot" +import * as zodv3 from "./schemas/zod/v3" +import * as zodv4 from "./schemas/zod/v4" +import type { + Caip10AccountId, + Caip19AssetId, + Caip19AssetName, + Caip19AssetType, + Caip2ChainId +} from "./schemas/valibot" + +const schemas = { + valibot, + zodv3, + zodv4 +} + +describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { + describe("CAIP-2 Chain ID Schema", () => { + it("validates correct CAIP-2 chain IDs", () => { + const validChainIds = [ + "eip155:1", + "eip155:11155111", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "bitcoin:mainnet", + "cosmos:cosmoshub-4" + ] + + for (const chainId of validChainIds) { + expect(chainId).toMatchSchema(schemas.caip2ChainIdSchema) + } + }) + + it("rejects invalid CAIP-2 chain IDs", () => { + const invalidChainIds = [ + "eip155", + ":1", + "eip155:", + "", + "ab:1", // too short namespace + "verylongnamespace:1", // too long namespace + "EIP155:1", // uppercase not allowed in namespace + "eip-155:1" // hyphen not allowed in namespace + ] + + for (const chainId of invalidChainIds) { + expect(chainId).not.toMatchSchema(schemas.caip2ChainIdSchema) + } + }) + }) + + describe("CAIP-10 Account ID Schema", () => { + it("validates correct CAIP-10 account IDs", () => { + const validAccountIds = [ + "eip155:1:0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "bitcoin:mainnet:bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" + ] + + for (const accountId of validAccountIds) { + expect(accountId).toMatchSchema(schemas.caip10AccountIdSchema) + } + }) + + it("rejects invalid CAIP-10 account IDs", () => { + const invalidAccountIds = [ + "eip155:1", + "eip155:1:", + "eip155::0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", + "" + ] + + for (const accountId of invalidAccountIds) { + expect(accountId).not.toMatchSchema(schemas.caip10AccountIdSchema) + } + }) + }) + + describe("CAIP-19 Asset Name Schema", () => { + it("validates correct CAIP-19 asset names", () => { + const validAssetNames = [ + "eip155:erc20", + "eip155:erc721", + "solana:spl", + "bitcoin:btc" + ] + + for (const assetName of validAssetNames) { + expect(assetName).toMatchSchema(schemas.caip19AssetNameSchema) + } + }) + + it("rejects invalid CAIP-19 asset names", () => { + const invalidAssetNames = [ + "eip155", + "eip155:", + ":erc20", + "EIP155:erc20", // uppercase not allowed + "" + ] + + for (const assetName of invalidAssetNames) { + expect(assetName).not.toMatchSchema(schemas.caip19AssetNameSchema) + } + }) + }) + + describe("CAIP-19 Asset Type Schema", () => { + it("validates correct CAIP-19 asset types", () => { + const validAssetTypes = [ + "eip155:1/eip155:erc20", + "eip155:11155111/eip155:erc721", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/solana:spl" + ] + + for (const assetType of validAssetTypes) { + expect(assetType).toMatchSchema(schemas.caip19AssetTypeSchema) + } + }) + + it("rejects invalid CAIP-19 asset types", () => { + const invalidAssetTypes = [ + "eip155:1", + "eip155:1/eip155", + "eip155:1/eip155:", + "invalid/asset/type", + "" + ] + + for (const assetType of invalidAssetTypes) { + expect(assetType).not.toMatchSchema(schemas.caip19AssetTypeSchema) + } + }) + }) + + describe("CAIP-19 Asset ID Schema", () => { + it("validates correct CAIP-19 asset IDs", () => { + const validAssetIds = [ + "eip155:1/eip155:erc20/0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", + "eip155:11155111/eip155:erc721/123", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/solana:spl/5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + ] + + for (const assetId of validAssetIds) { + expect(assetId).toMatchSchema(schemas.caip19AssetIdSchema) + } + }) + + it("rejects invalid CAIP-19 asset IDs", () => { + const invalidAssetIds = [ + "eip155:1/eip155:erc20", + "eip155:1/eip155:erc20/", + "eip155:1//0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", + "invalid/asset/id/format", + "" + ] + + for (const assetId of invalidAssetIds) { + expect(assetId).not.toMatchSchema(schemas.caip19AssetIdSchema) + } + }) + }) + + describe("Type Inference", () => { + it("has correct type inference for CAIP-2", () => { + const chainId: Caip2ChainId = "eip155:1" + expect(typeof chainId).toBe("string") + }) + + it("has correct type inference for CAIP-10", () => { + const accountId: Caip10AccountId = + "eip155:1:0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6" + expect(typeof accountId).toBe("string") + }) + + it("has correct type inference for CAIP-19 Asset Name", () => { + const assetName: Caip19AssetName = "eip155:erc20" + expect(typeof assetName).toBe("string") + }) + + it("has correct type inference for CAIP-19 Asset Type", () => { + const assetType: Caip19AssetType = "eip155:1/eip155:erc20" + expect(typeof assetType).toBe("string") + }) + + it("has correct type inference for CAIP-19 Asset ID", () => { + const assetId: Caip19AssetId = + "eip155:1/eip155:erc20/0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6" + expect(typeof assetId).toBe("string") + }) + }) +}) diff --git a/packages/did/src/caip/schemas/core.ts b/packages/did/src/caip/schemas/core.ts new file mode 100644 index 0000000..756ef78 --- /dev/null +++ b/packages/did/src/caip/schemas/core.ts @@ -0,0 +1,105 @@ +/** + * CAIP Schema Patterns + * + * Core regex patterns for CAIP specifications that can be composed + * to build more complex CAIP schemas across validation libraries. + * + * @see {@link https://github.com/ChainAgnostic/CAIPs} + */ + +/** + * CAIP-2 Spec - Chain ID Components + * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md} + * + * chain_id: namespace + ":" + reference + * namespace: [a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + */ + +/** + * CAIP-2 namespace pattern: [a-z0-9]{3,8} + * @example "eip155", "solana", "cosmos" + */ +export const CAIP2_NAMESPACE_PATTERN = "[a-z0-9]{3,8}" + +/** + * CAIP-2 reference pattern: [-_a-zA-Z0-9]{1,32} + * @example "1", "11155111", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + */ +export const CAIP2_REFERENCE_PATTERN = "[-_a-zA-Z0-9]{1,32}" + +/** + * CAIP-2 chain_id pattern: namespace + ":" + reference + * @example "eip155:1", "eip155:11155111", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + */ +export const CAIP2_CHAIN_ID_PATTERN = `${CAIP2_NAMESPACE_PATTERN}:${CAIP2_REFERENCE_PATTERN}` + +/** + * CAIP-10 Spec - Account ID Components + * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md} + * + * account_id: chain_id + ":" + account_address + * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See CAIP-2) + * account_address: [-.%a-zA-Z0-9]{1,128} + */ + +/** + * CAIP-10 account_address pattern: [-.%a-zA-Z0-9]{1,128} + * @example "0x1234567890123456789012345678901234567890", "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + */ +export const CAIP10_ACCOUNT_ADDRESS_PATTERN = "[-.%a-zA-Z0-9]{1,128}" + +/** + * CAIP-10 account_id pattern: chain_id + ":" + account_address + * @example "eip155:1:0x1234567890123456789012345678901234567890", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + */ +export const CAIP10_ACCOUNT_ID_PATTERN = `${CAIP2_CHAIN_ID_PATTERN}:${CAIP10_ACCOUNT_ADDRESS_PATTERN}` + +/** + * CAIP-19 Spec - Asset Identification Components + * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md} + * + * asset_name: asset_namespace + ":" + asset_reference + * asset_type: chain_id + "/" + asset_name + * asset_id: asset_type + "/" + token_id + * + * asset_namespace: [-a-z0-9]{3,8} + * asset_reference: [-.%a-zA-Z0-9]{1,128} + * token_id: [-.%a-zA-Z0-9]{1,78} + */ + +/** + * CAIP-19 asset_namespace pattern: [-a-z0-9]{3,8} + * @example "erc20", "erc721", "spl" + */ +export const CAIP19_ASSET_NAMESPACE_PATTERN = "[-a-z0-9]{3,8}" + +/** + * CAIP-19 asset_reference pattern: [-.%a-zA-Z0-9]{1,128} + * @example "0xA0b86a33E6441e6e80A7C1A00000000001", "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + */ +export const CAIP19_ASSET_REFERENCE_PATTERN = "[-.%a-zA-Z0-9]{1,128}" + +/** + * CAIP-19 asset_name pattern: asset_namespace + ":" + asset_reference + * @example "erc20:0xA0b86a33E6441e6e80A7C1A00000000001", "spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + */ +export const CAIP19_ASSET_NAME_PATTERN = `${CAIP19_ASSET_NAMESPACE_PATTERN}:${CAIP19_ASSET_REFERENCE_PATTERN}` + +/** + * CAIP-19 asset_type pattern: chain_id + "/" + asset_name + * @example "eip155:1/erc20:0xA0b86a33E6441e6e80A7C1A00000000001", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + */ +export const CAIP19_ASSET_TYPE_PATTERN = `${CAIP2_CHAIN_ID_PATTERN}/${CAIP19_ASSET_NAME_PATTERN}` + +/** + * CAIP-19 token_id pattern: [-.%a-zA-Z0-9]{1,78} + * @example "1", "42", "CryptoPunk.3100" + */ +export const CAIP19_TOKEN_ID_PATTERN = "[-.%a-zA-Z0-9]{1,78}" + +/** + * CAIP-19 asset_id pattern: asset_type + "/" + token_id + * @example "eip155:1/erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1", "eip155:1/erc721:0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/42" + */ +export const CAIP19_ASSET_ID_PATTERN = `${CAIP19_ASSET_TYPE_PATTERN}/${CAIP19_TOKEN_ID_PATTERN}` diff --git a/packages/did/src/caip/schemas/valibot.ts b/packages/did/src/caip/schemas/valibot.ts new file mode 100644 index 0000000..2236716 --- /dev/null +++ b/packages/did/src/caip/schemas/valibot.ts @@ -0,0 +1,48 @@ +import * as v from "valibot" +import { + CAIP10_ACCOUNT_ID_PATTERN, + CAIP19_ASSET_ID_PATTERN, + CAIP19_ASSET_NAME_PATTERN, + CAIP19_ASSET_TYPE_PATTERN, + CAIP2_CHAIN_ID_PATTERN +} from "./core" + +export const caip2ChainIdSchema = v.pipe( + v.string(), + v.regex(new RegExp(`^${CAIP2_CHAIN_ID_PATTERN}$`)), + v.custom<`${string}:${string}`>(() => true) +) + +export type Caip2ChainId = v.InferOutput + +export const caip10AccountIdSchema = v.pipe( + v.string(), + v.regex(new RegExp(`^${CAIP10_ACCOUNT_ID_PATTERN}$`)), + v.custom<`${Caip2ChainId}:${string}`>(() => true) +) + +export type Caip10AccountId = v.InferOutput + +export const caip19AssetNameSchema = v.pipe( + v.string(), + v.regex(new RegExp(`^${CAIP19_ASSET_NAME_PATTERN}$`)), + v.custom<`${string}:${string}`>(() => true) +) + +export type Caip19AssetName = v.InferOutput + +export const caip19AssetTypeSchema = v.pipe( + v.string(), + v.regex(new RegExp(`^${CAIP19_ASSET_TYPE_PATTERN}$`)), + v.custom<`${Caip2ChainId}/${Caip19AssetName}`>(() => true) +) + +export type Caip19AssetType = v.InferOutput + +export const caip19AssetIdSchema = v.pipe( + v.string(), + v.regex(new RegExp(`^${CAIP19_ASSET_ID_PATTERN}$`)), + v.custom<`${Caip2ChainId}/${Caip19AssetName}/${string}`>(() => true) +) + +export type Caip19AssetId = v.InferOutput diff --git a/packages/did/src/caip/schemas/zod/v3.ts b/packages/did/src/caip/schemas/zod/v3.ts new file mode 100644 index 0000000..57cb6cd --- /dev/null +++ b/packages/did/src/caip/schemas/zod/v3.ts @@ -0,0 +1,28 @@ +import { z } from "zod/v3" +import { + CAIP10_ACCOUNT_ID_PATTERN, + CAIP19_ASSET_ID_PATTERN, + CAIP19_ASSET_NAME_PATTERN, + CAIP19_ASSET_TYPE_PATTERN, + CAIP2_CHAIN_ID_PATTERN +} from "../core" + +export const caip2ChainIdSchema = z + .string() + .regex(new RegExp(`^${CAIP2_CHAIN_ID_PATTERN}$`)) + +export const caip10AccountIdSchema = z + .string() + .regex(new RegExp(`^${CAIP10_ACCOUNT_ID_PATTERN}$`)) + +export const caip19AssetNameSchema = z + .string() + .regex(new RegExp(`^${CAIP19_ASSET_NAME_PATTERN}$`)) + +export const caip19AssetTypeSchema = z + .string() + .regex(new RegExp(`^${CAIP19_ASSET_TYPE_PATTERN}$`)) + +export const caip19AssetIdSchema = z + .string() + .regex(new RegExp(`^${CAIP19_ASSET_ID_PATTERN}$`)) diff --git a/packages/did/src/caip/schemas/zod/v4.ts b/packages/did/src/caip/schemas/zod/v4.ts new file mode 100644 index 0000000..15382ad --- /dev/null +++ b/packages/did/src/caip/schemas/zod/v4.ts @@ -0,0 +1,28 @@ +import * as z from "zod/v4" +import { + CAIP10_ACCOUNT_ID_PATTERN, + CAIP19_ASSET_ID_PATTERN, + CAIP19_ASSET_NAME_PATTERN, + CAIP19_ASSET_TYPE_PATTERN, + CAIP2_CHAIN_ID_PATTERN +} from "../core" + +export const caip2ChainIdSchema = z + .string() + .regex(new RegExp(`^${CAIP2_CHAIN_ID_PATTERN}$`)) + +export const caip10AccountIdSchema = z + .string() + .regex(new RegExp(`^${CAIP10_ACCOUNT_ID_PATTERN}$`)) + +export const caip19AssetNameSchema = z + .string() + .regex(new RegExp(`^${CAIP19_ASSET_NAME_PATTERN}$`)) + +export const caip19AssetTypeSchema = z + .string() + .regex(new RegExp(`^${CAIP19_ASSET_TYPE_PATTERN}$`)) + +export const caip19AssetIdSchema = z + .string() + .regex(new RegExp(`^${CAIP19_ASSET_ID_PATTERN}$`)) diff --git a/packages/did/src/caip/types.ts b/packages/did/src/caip/types.ts new file mode 100644 index 0000000..2857d2b --- /dev/null +++ b/packages/did/src/caip/types.ts @@ -0,0 +1,7 @@ +export type { + Caip2ChainId, + Caip10AccountId, + Caip19AssetId, + Caip19AssetName, + Caip19AssetType +} from "./schemas/valibot" diff --git a/packages/did/src/did-uri.ts b/packages/did/src/did-uri.ts index 7514b47..31d2578 100644 --- a/packages/did/src/did-uri.ts +++ b/packages/did/src/did-uri.ts @@ -1,4 +1,7 @@ -export type DidUri = `did:${string}:${string}` +export type DidUri< + TMethod extends string = string, + TIdentifier extends string = string +> = `did:${TMethod}:${TIdentifier}` /** * Check if a value is a did uri diff --git a/packages/did/src/index.ts b/packages/did/src/index.ts index 0366c95..8fc8418 100644 --- a/packages/did/src/index.ts +++ b/packages/did/src/index.ts @@ -1,3 +1,4 @@ +export * from "./caip" export * from "./did-document" export * from "./did-resolvers/did-resolver" export * from "./did-resolvers/get-did-resolver" diff --git a/packages/did/src/methods/did-pkh.test.ts b/packages/did/src/methods/did-pkh.test.ts new file mode 100644 index 0000000..a5d3dce --- /dev/null +++ b/packages/did/src/methods/did-pkh.test.ts @@ -0,0 +1,284 @@ +import { describe, expect, it } from "vitest" +import { + addressFromDidPkhUri, + createBlockchainAccountId, + createDidPkhDocument, + createDidPkhUri, + didPkhChainIds, + didPkhParts, + isDidPkhUri +} from "./did-pkh" +import type { Keypair } from "@agentcommercekit/keys" + +describe("didPkhParts", () => { + it("parses valid did:pkh URIs correctly", () => { + const result = didPkhParts( + "did:pkh:eip155:1:0x1234567890123456789012345678901234567890" + ) + expect(result).toEqual([ + "did", + "pkh", + "eip155", + "1", + "0x1234567890123456789012345678901234567890" + ]) + }) + + it("parses Solana did:pkh URIs correctly", () => { + const result = didPkhParts( + "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + expect(result).toEqual([ + "did", + "pkh", + "solana", + "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ]) + }) + + it("throws error for non-did:pkh URIs", () => { + expect(() => didPkhParts("did:key:123")).toThrow("Invalid did:pkh URI") + expect(() => didPkhParts("did:pkh")).toThrow("Invalid did:pkh URI") + expect(() => didPkhParts("not-a-did")).toThrow("Invalid did:pkh URI") + }) + + it("throws error for incomplete did:pkh URIs", () => { + expect(() => didPkhParts("did:pkh:")).toThrow("Invalid did:pkh URI") + expect(() => didPkhParts("did:pkh:eip155")).toThrow("Invalid did:pkh URI") + expect(() => didPkhParts("did:pkh:eip155:")).toThrow("Invalid did:pkh URI") + }) + + it("throws error for invalid chain IDs", () => { + expect(() => didPkhParts("did:pkh:ab:1:0x123")).toThrow( + "Invalid did:pkh URI" + ) + }) + + it("throws error for non-string inputs", () => { + expect(() => didPkhParts(null as unknown as string)).toThrow( + "Invalid did:pkh URI" + ) + expect(() => didPkhParts(undefined as unknown as string)).toThrow( + "Invalid did:pkh URI" + ) + expect(() => didPkhParts(123 as unknown as string)).toThrow( + "Invalid did:pkh URI" + ) + }) +}) + +describe("isDidPkhUri", () => { + it("returns true for valid did:pkh URIs", () => { + expect( + isDidPkhUri("did:pkh:eip155:1:0x1234567890123456789012345678901234567890") + ).toBe(true) + expect( + isDidPkhUri( + "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + ).toBe(true) + expect( + isDidPkhUri("did:pkh:bitcoin:mainnet:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") + ).toBe(true) + }) + + it("returns false for invalid URIs", () => { + expect(isDidPkhUri("did:key:123")).toBe(false) + expect(isDidPkhUri("did:pkh:ab:1:0x123")).toBe(false) + expect(isDidPkhUri("not-a-did")).toBe(false) + expect(isDidPkhUri("")).toBe(false) + expect(isDidPkhUri(null)).toBe(false) + expect(isDidPkhUri(undefined)).toBe(false) + expect(isDidPkhUri(123)).toBe(false) + }) +}) + +describe("addressFromDidPkhUri", () => { + it("extracts address from EVM did:pkh URI", () => { + const address = addressFromDidPkhUri( + "did:pkh:eip155:1:0x1234567890123456789012345678901234567890" + ) + expect(address).toBe("0x1234567890123456789012345678901234567890") + }) + + it("extracts address from Solana did:pkh URI", () => { + const address = addressFromDidPkhUri( + "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + expect(address).toBe("FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P") + }) + + it("extracts address from Bitcoin did:pkh URI", () => { + const address = addressFromDidPkhUri( + "did:pkh:bitcoin:mainnet:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" + ) + expect(address).toBe("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") + }) + + it("throws error for invalid did:pkh URI", () => { + expect(() => addressFromDidPkhUri("did:key:123")).toThrow( + "Invalid did:pkh URI" + ) + }) +}) + +describe("didPkhChainIds", () => { + it("contains expected EVM chain IDs", () => { + expect(didPkhChainIds.evm.mainnet).toBe("eip155:1") + expect(didPkhChainIds.evm.sepolia).toBe("eip155:11155111") + expect(didPkhChainIds.evm.base).toBe("eip155:8453") + expect(didPkhChainIds.evm.baseSepolia).toBe("eip155:84532") + expect(didPkhChainIds.evm.arbitrum).toBe("eip155:42161") + expect(didPkhChainIds.evm.arbitrumSepolia).toBe("eip155:421614") + }) + + it("contains expected SVM chain IDs", () => { + expect(didPkhChainIds.svm.mainnet).toBe( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + ) + expect(didPkhChainIds.svm.devnet).toBe( + "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" + ) + }) +}) + +describe("createBlockchainAccountId", () => { + it("creates blockchain account ID for EVM address", () => { + const result = createBlockchainAccountId( + "0x1234567890123456789012345678901234567890", + "eip155:1" + ) + expect(result).toBe("eip155:1:0x1234567890123456789012345678901234567890") + }) + + it("creates blockchain account ID for Solana address", () => { + const result = createBlockchainAccountId( + "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + ) + expect(result).toBe( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + }) +}) + +describe("createDidPkhUri", () => { + it("creates did:pkh URI for EVM address", () => { + const result = createDidPkhUri( + "0x1234567890123456789012345678901234567890", + "eip155:1" + ) + expect(result).toBe( + "did:pkh:eip155:1:0x1234567890123456789012345678901234567890" + ) + }) + + it("creates did:pkh URI for Solana address", () => { + const result = createDidPkhUri( + "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + ) + expect(result).toBe( + "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + }) +}) + +describe("createDidPkhDocument", () => { + const mockSecp256k1Keypair: Keypair = { + curve: "secp256k1", + publicKey: new Uint8Array([1, 2, 3, 4]), + privateKey: new Uint8Array([5, 6, 7, 8]) + } + + const mockEd25519Keypair: Keypair = { + curve: "Ed25519", + publicKey: new Uint8Array([1, 2, 3, 4]), + privateKey: new Uint8Array([5, 6, 7, 8]) + } + + it("creates did:pkh document for EVM address with secp256k1 keypair", () => { + const result = createDidPkhDocument({ + keypair: mockSecp256k1Keypair, + address: "0x1234567890123456789012345678901234567890", + chainId: "eip155:1" + }) + + expect(result.did).toBe( + "did:pkh:eip155:1:0x1234567890123456789012345678901234567890" + ) + expect(result.didDocument["@context"]).toContain( + "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020" + ) + expect(result.didDocument["@context"]).toContain( + "https://w3id.org/security#blockchainAccountId" + ) + expect(result.didDocument.verificationMethod).toHaveLength(1) + expect(result.didDocument.verificationMethod?.[0]?.type).toBe( + "EcdsaSecp256k1RecoveryMethod2020" + ) + expect( + result.didDocument.verificationMethod?.[0]?.blockchainAccountId + ).toBe("eip155:1:0x1234567890123456789012345678901234567890") + }) + + it("creates did:pkh document for Solana address with Ed25519 keypair", () => { + const result = createDidPkhDocument({ + keypair: mockEd25519Keypair, + address: "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", + chainId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + }) + + expect(result.did).toBe( + "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + expect(result.didDocument["@context"]).toContain( + "https://w3id.org/security#publicKeyJwk" + ) + expect(result.didDocument["@context"]).toContain( + "https://w3id.org/security#blockchainAccountId" + ) + expect(result.didDocument.verificationMethod).toHaveLength(1) + expect( + result.didDocument.verificationMethod?.[0]?.blockchainAccountId + ).toBe( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + }) + + it("creates did:pkh document with custom controller", () => { + const result = createDidPkhDocument({ + keypair: mockSecp256k1Keypair, + address: "0x1234567890123456789012345678901234567890", + chainId: "eip155:1", + controller: "did:key:zQ3sharFd8K3z6L9b5X5J7m8n9o0p1q2r3s4t5u6v7w8x9y0z" + }) + + expect(result.didDocument.controller).toBe( + "did:key:zQ3sharFd8K3z6L9b5X5J7m8n9o0p1q2r3s4t5u6v7w8x9y0z" + ) + }) + + it("throws error for mismatched keypair algorithm and chain", () => { + expect(() => + createDidPkhDocument({ + keypair: mockEd25519Keypair, + address: "0x1234567890123456789012345678901234567890", + chainId: "eip155:1" + }) + ).toThrow( + "Invalid keypair algorithm. Expected secp256k1 for chain eip155:1" + ) + + expect(() => + createDidPkhDocument({ + keypair: mockSecp256k1Keypair, + address: "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", + chainId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + }) + ).toThrow( + "Invalid keypair algorithm. Expected Ed25519 for chain solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + ) + }) +}) diff --git a/packages/did/src/methods/did-pkh.ts b/packages/did/src/methods/did-pkh.ts index 374af1a..97015fa 100644 --- a/packages/did/src/methods/did-pkh.ts +++ b/packages/did/src/methods/did-pkh.ts @@ -1,8 +1,8 @@ /* eslint-disable @cspell/spellchecker */ -import * as v from "valibot" +import { isCaip2ChainId } from "../caip/caip" import { createDidDocumentFromKeypair } from "../create-did-document" -import { didPkhChainIdSchema } from "../schemas/valibot" import type { DidUri } from "../did-uri" +import type { Caip10AccountId, Caip2ChainId } from "../schemas/valibot" import type { DidUriWithDocument } from "../types" import type { Keypair } from "@agentcommercekit/keys" @@ -12,24 +12,20 @@ import type { Keypair } from "@agentcommercekit/keys" * @see {@link https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md} */ +/** + * @deprecated Use `Caip2ChainId` instead + */ +export type DidPkhChainId = Caip2ChainId + /** * The `did:pkh` Uri type */ -export type DidPkhChainId = v.InferOutput -export type DidPkhUri = `did:pkh:${DidPkhChainId}:${string}` +export type DidPkhUri = DidUri<"pkh", Caip10AccountId> /** - * Checks if a given string is a valid CAIP-2 chain ID (`namespace:reference`) - * chain_id: namespace + ":" + reference - * namespace: [-a-z0-9]{3,8} - * reference: [-_a-zA-Z0-9]{1,32} - * - * @param chainId - The chain ID to check - * @returns `true` if the chain ID is a valid CAIP-2 chain ID, `false` otherwise + * @deprecated Use `isCaip2ChainId` instead */ -export function isDidPkhChainId(chainId: unknown): chainId is DidPkhChainId { - return v.is(didPkhChainIdSchema, chainId) -} +export const isDidPkhChainId = isCaip2ChainId /** * Parse a did:pkh URI into its components. @@ -63,7 +59,7 @@ export function didPkhParts( throw new Error("Invalid did:pkh URI") } - if (!isDidPkhChainId(`${chainNamespace}:${chainReference}`)) { + if (!isCaip2ChainId(`${chainNamespace}:${chainReference}`)) { throw new Error("Invalid did:pkh URI") } @@ -137,8 +133,8 @@ export const didPkhChainIds = { */ export function createBlockchainAccountId( address: string, - chainId: DidPkhChainId -): `${DidPkhChainId}:${string}` { + chainId: Caip2ChainId +): `${Caip2ChainId}:${string}` { return `${chainId}:${address}` } @@ -160,7 +156,7 @@ export function createBlockchainAccountId( */ export function createDidPkhUri( address: string, - chainId: DidPkhChainId + chainId: Caip2ChainId ): DidUri { return `did:pkh:${createBlockchainAccountId(address, chainId)}` } @@ -168,7 +164,7 @@ export function createDidPkhUri( interface CreateDidPkhDocumentOptions { keypair: Keypair address: string - chainId: DidPkhChainId + chainId: Caip2ChainId controller?: DidUri } diff --git a/packages/did/src/schemas/valibot.ts b/packages/did/src/schemas/valibot.ts index bb5da1d..d284a7d 100644 --- a/packages/did/src/schemas/valibot.ts +++ b/packages/did/src/schemas/valibot.ts @@ -1,11 +1,13 @@ import * as v from "valibot" +import { caip2ChainIdSchema } from "../caip/schemas/valibot" import { isDidUri } from "../did-uri" import type { DidUri } from "../did-uri" +export * from "../caip/schemas/valibot" + export const didUriSchema = v.custom(isDidUri, "Invalid DID format") -export const didPkhChainIdSchema = v.pipe( - v.string(), - v.regex(/^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/), - v.transform((val) => val as `${string}:${string}`) -) +/** + * @deprecated Use `caip2ChainIdSchema` instead + */ +export const didPkhChainIdSchema = caip2ChainIdSchema diff --git a/packages/did/src/schemas/zod/v3.ts b/packages/did/src/schemas/zod/v3.ts index 747211a..c305826 100644 --- a/packages/did/src/schemas/zod/v3.ts +++ b/packages/did/src/schemas/zod/v3.ts @@ -1,10 +1,13 @@ -import { z } from "zod" +import { z } from "zod/v3" +import { caip2ChainIdSchema } from "../../caip/schemas/zod/v3" import { isDidUri } from "../../did-uri" import type { DidUri } from "../../did-uri" +export * from "../../caip/schemas/zod/v3" + export const didUriSchema = z.custom(isDidUri, "Invalid DID format") -export const didPkhChainIdSchema = z - .string() - .regex(/^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/) - .refine((val): val is `${string}:${string}` => true) +/** + * @deprecated Use `caip2ChainIdSchema` instead + */ +export const didPkhChainIdSchema = caip2ChainIdSchema diff --git a/packages/did/src/schemas/zod/v4.ts b/packages/did/src/schemas/zod/v4.ts index ca3e3bc..6f5a584 100644 --- a/packages/did/src/schemas/zod/v4.ts +++ b/packages/did/src/schemas/zod/v4.ts @@ -1,10 +1,13 @@ import * as z from "zod/v4" +import { caip2ChainIdSchema } from "../../caip/schemas/zod/v4" import { isDidUri } from "../../did-uri" import type { DidUri } from "../../did-uri" +export * from "../../caip/schemas/zod/v4" + export const didUriSchema = z.custom(isDidUri, "Invalid DID format") -export const didPkhChainIdSchema = z - .string() - .regex(/^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/) - .refine((val): val is `${string}:${string}` => true) +/** + * @deprecated Use `caip2ChainIdSchema` instead + */ +export const didPkhChainIdSchema = caip2ChainIdSchema diff --git a/packages/did/vitest.config.ts b/packages/did/vitest.config.ts index 6a8b09e..122104c 100644 --- a/packages/did/vitest.config.ts +++ b/packages/did/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "vitest/config" export default defineConfig({ test: { + setupFiles: ["./vitest.setup.ts"], passWithNoTests: true, watch: false } diff --git a/packages/did/vitest.setup.ts b/packages/did/vitest.setup.ts new file mode 100644 index 0000000..27f3dec --- /dev/null +++ b/packages/did/vitest.setup.ts @@ -0,0 +1,13 @@ +/** + * Adds `toMatchSchema` matchers + * + * @example + * ```ts + * expect(value).toMatchSchema(schema) + * expect(value).not.toMatchSchema(schema) + * expect(value).toMatchSchema(schema, (parsed) => { + * // ... additional assertions + * }) + * ``` + */ +import "standard-parse/test-matchers/vitest" diff --git a/packages/jwt/package.json b/packages/jwt/package.json index c39d856..cde689f 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -65,7 +65,7 @@ "tsdown": "^0.11.12", "typescript": "^5", "valibot": "^1.1.0", - "vitest": "^3.1.4", + "vitest": "^3.2.4", "zod": "^3.25.0" }, "peerDependencies": { diff --git a/packages/jwt/src/schemas/zod/v3.ts b/packages/jwt/src/schemas/zod/v3.ts index a56c3f0..ef2a977 100644 --- a/packages/jwt/src/schemas/zod/v3.ts +++ b/packages/jwt/src/schemas/zod/v3.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import { z } from "zod/v3" import { jwtAlgorithms } from "../../jwt-algorithm" import { isJwtString } from "../../jwt-string" import type { JwtHeader, JwtPayload } from "../../create-jwt" diff --git a/packages/keys/package.json b/packages/keys/package.json index 324971f..0ba53e1 100644 --- a/packages/keys/package.json +++ b/packages/keys/package.json @@ -66,7 +66,7 @@ "eslint": "^9.27.0", "tsdown": "^0.11.12", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" }, "publishConfig": { "access": "public" diff --git a/packages/vc/package.json b/packages/vc/package.json index 87bbc55..d87dc9a 100644 --- a/packages/vc/package.json +++ b/packages/vc/package.json @@ -68,7 +68,7 @@ "eslint": "^9.27.0", "tsdown": "^0.11.12", "typescript": "^5", - "vitest": "^3.1.4", + "vitest": "^3.2.4", "zod": "^3.25.0" }, "peerDependencies": { diff --git a/packages/vc/src/schemas/zod/v3.ts b/packages/vc/src/schemas/zod/v3.ts index 49894dd..efa22a4 100644 --- a/packages/vc/src/schemas/zod/v3.ts +++ b/packages/vc/src/schemas/zod/v3.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import { z } from "zod/v3" import type { W3CCredential } from "../../types" export const credentialSchema = z diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0937f59..f4dd4cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,8 +61,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) demos/identity: dependencies: @@ -122,8 +122,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) demos/identity-a2a: dependencies: @@ -174,8 +174,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) demos/payments: dependencies: @@ -223,8 +223,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) demos/skyfire-kya: dependencies: @@ -257,8 +257,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) docs: devDependencies: @@ -321,8 +321,8 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.3)(vite@6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0)) vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) examples/local-did-host: dependencies: @@ -367,8 +367,8 @@ importers: specifier: ^4.19.4 version: 4.20.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) examples/verifier: dependencies: @@ -407,8 +407,8 @@ importers: specifier: ^4.19.4 version: 4.20.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) packages/ack-id: dependencies: @@ -453,8 +453,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.25.0 version: 3.25.4 @@ -493,8 +493,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.25.0 version: 3.25.4 @@ -542,8 +542,8 @@ importers: specifier: ^1.1.0 version: 1.1.0(typescript@5.8.3) vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.25.0 version: 3.25.4 @@ -581,6 +581,9 @@ importers: eslint: specifier: ^9.27.0 version: 9.27.0(jiti@2.4.2) + standard-parse: + specifier: ^0.3.0 + version: 0.3.0(valibot@1.1.0(typescript@5.8.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0))(zod@3.25.4) tsdown: specifier: ^0.11.12 version: 0.11.12(typescript@5.8.3) @@ -588,8 +591,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.25.0 version: 3.25.4 @@ -622,8 +625,8 @@ importers: specifier: ^1.1.0 version: 1.1.0(typescript@5.8.3) vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.25.0 version: 3.25.4 @@ -659,8 +662,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) packages/vc: dependencies: @@ -699,8 +702,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) zod: specifier: ^3.25.0 version: 3.25.4 @@ -742,8 +745,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) tools/cli-tools: dependencies: @@ -782,8 +785,8 @@ importers: specifier: ^5 version: 5.8.3 vitest: - specifier: ^3.1.4 - version: 3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) tools/eslint-config: devDependencies: @@ -2199,6 +2202,9 @@ packages: peerDependencies: typescript: '>=5.3.3' + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@stoplight/better-ajv-errors@1.0.3': resolution: {integrity: sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==} engines: {node: ^12.20 || >= 14.13} @@ -2284,6 +2290,9 @@ packages: '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -2293,6 +2302,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/diff-match-patch@1.0.36': resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} @@ -2529,34 +2541,34 @@ packages: peerDependencies: valibot: ^1.0.0 - '@vitest/expect@3.1.4': - resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.1.4': - resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.1.4': - resolution: {integrity: sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.1.4': - resolution: {integrity: sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@3.1.4': - resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@3.1.4': - resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@3.1.4': - resolution: {integrity: sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} abitype@1.0.8: resolution: {integrity: sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==} @@ -4316,6 +4328,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -4508,6 +4523,9 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.2.0: + resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} + lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5663,6 +5681,23 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + standard-parse@0.3.0: + resolution: {integrity: sha512-KKYy5m/qJDiTWahewFmFNKsFo+VeoX7HtLeV92n/J3sVRu7pQtKoibYaGOv6Lx/LyfYqozeIZCELTtBHDpnutw==} + peerDependencies: + arktype: ^2.0.0 + valibot: ^1.0.0 + vitest: ^3.2.0 + zod: ^3.25.0 + peerDependenciesMeta: + arktype: + optional: true + valibot: + optional: true + vitest: + optional: true + zod: + optional: true + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -5727,6 +5762,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + style-to-js@1.1.16: resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} @@ -5798,16 +5836,20 @@ packages: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} tmp@0.0.33: @@ -6114,8 +6156,8 @@ packages: typescript: optional: true - vite-node@3.1.4: - resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -6167,16 +6209,16 @@ packages: yaml: optional: true - vitest@3.1.4: - resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.1.4 - '@vitest/ui': 3.1.4 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -7869,6 +7911,8 @@ snapshots: commander: 13.1.0 typescript: 5.8.3 + '@standard-schema/spec@1.0.0': {} + '@stoplight/better-ajv-errors@1.0.3(ajv@8.17.1)': dependencies: ajv: 8.17.1 @@ -8032,6 +8076,10 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 22.15.19 + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + '@types/connect@3.4.38': dependencies: '@types/node': 22.15.19 @@ -8044,6 +8092,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/diff-match-patch@1.0.36': {} '@types/es-aggregate-error@1.0.6': @@ -8286,44 +8336,46 @@ snapshots: dependencies: valibot: 1.1.0(typescript@5.8.3) - '@vitest/expect@3.1.4': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 3.1.4 - '@vitest/utils': 3.1.4 + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.4(vite@6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0))': dependencies: - '@vitest/spy': 3.1.4 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: vite: 6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/pretty-format@3.1.4': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.1.4': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.1.4 + '@vitest/utils': 3.2.4 pathe: 2.0.3 + strip-literal: 3.0.0 - '@vitest/snapshot@3.1.4': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.4 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/spy@3.1.4': + '@vitest/spy@3.2.4': dependencies: - tinyspy: 3.0.2 + tinyspy: 4.0.3 - '@vitest/utils@3.1.4': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.1.4 - loupe: 3.1.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.0 tinyrainbow: 2.0.0 abitype@1.0.8(typescript@5.8.3)(zod@3.25.4): @@ -10283,6 +10335,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -10443,6 +10497,8 @@ snapshots: loupe@3.1.3: {} + loupe@3.2.0: {} + lowercase-keys@3.0.0: {} lru-cache@7.18.3: {} @@ -12120,6 +12176,14 @@ snapshots: stackback@0.0.2: {} + standard-parse@0.3.0(valibot@1.1.0(typescript@5.8.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0))(zod@3.25.4): + dependencies: + '@standard-schema/spec': 1.0.0 + optionalDependencies: + valibot: 1.1.0(typescript@5.8.3) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + zod: 3.25.4 + statuses@2.0.1: {} std-env@3.9.0: {} @@ -12195,6 +12259,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + style-to-js@1.1.16: dependencies: style-to-object: 1.0.8 @@ -12285,11 +12353,16 @@ snapshots: fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 - tinypool@1.0.2: {} + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} - tinyspy@3.0.2: {} + tinyspy@4.0.3: {} tmp@0.0.33: dependencies: @@ -12653,7 +12726,7 @@ snapshots: - utf-8-validate - zod - vite-node@3.1.4(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0): + vite-node@3.2.4(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.1 @@ -12698,28 +12771,30 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 - vitest@3.1.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0): dependencies: - '@vitest/expect': 3.1.4 - '@vitest/mocker': 3.1.4(vite@6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0)) - '@vitest/pretty-format': 3.1.4 - '@vitest/runner': 3.1.4 - '@vitest/snapshot': 3.1.4 - '@vitest/spy': 3.1.4 - '@vitest/utils': 3.1.4 + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 + picomatch: 4.0.2 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.13 - tinypool: 1.0.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 6.2.5(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) - vite-node: 3.1.4(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 diff --git a/tools/api-utils/package.json b/tools/api-utils/package.json index 8b2fbcb..e292a0a 100644 --- a/tools/api-utils/package.json +++ b/tools/api-utils/package.json @@ -56,6 +56,6 @@ "@repo/typescript-config": "workspace:*", "eslint": "^9.27.0", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } diff --git a/tools/cli-tools/package.json b/tools/cli-tools/package.json index d76cadf..3cf5b04 100644 --- a/tools/cli-tools/package.json +++ b/tools/cli-tools/package.json @@ -43,6 +43,6 @@ "@types/node": "^22", "eslint": "^9.27.0", "typescript": "^5", - "vitest": "^3.1.4" + "vitest": "^3.2.4" } } From a48fab434b4f32910b43863a180f849efa5ad313 Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Thu, 24 Jul 2025 23:18:28 -0400 Subject: [PATCH 3/6] Add custom did:pkh resolver for wider chain support The pkh-did-resolver package excluded many chains, including solana, which are necessary for an adequate did:pkh offering. This swaps out that package with an internal implementation which was trivial to create because we were already creating did:pkh documents locally --- .changeset/olive-dogs-rush.md | 11 + demos/e2e/src/agent.ts | 1 - demos/e2e/src/index.ts | 4 +- demos/e2e/src/user.ts | 3 +- demos/payments/src/constants.ts | 4 +- demos/payments/src/utils/keypair-info.ts | 2 +- .../src/verify-payment-receipt.test.ts | 4 +- packages/did/README.md | 6 +- packages/did/package.json | 1 - packages/did/src/caip/caip.test.ts | 22 +- packages/did/src/caip/caip.ts | 68 +++++- packages/did/src/create-did-document.ts | 128 +++++++---- .../did/src/did-resolvers/get-did-resolver.ts | 2 +- .../did-resolvers/pkh-did-resolver.test.ts | 45 ++++ .../did/src/did-resolvers/pkh-did-resolver.ts | 38 ++++ packages/did/src/methods/did-pkh.test.ts | 88 +------- packages/did/src/methods/did-pkh.ts | 205 ++++++++++-------- ...5714089478a327f09197987f16f9e5d936e8a.json | 28 +++ ...e58da58dfa46fa55c3b86545e7065f90ff011.json | 28 +++ ...Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev.json | 34 +++ pnpm-lock.yaml | 16 -- 21 files changed, 495 insertions(+), 243 deletions(-) create mode 100644 .changeset/olive-dogs-rush.md create mode 100644 packages/did/src/did-resolvers/pkh-did-resolver.test.ts create mode 100644 packages/did/src/did-resolvers/pkh-did-resolver.ts create mode 100644 packages/did/test-fixtures/did-pkh/did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a.json create mode 100644 packages/did/test-fixtures/did-pkh/did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011.json create mode 100644 packages/did/test-fixtures/did-pkh/did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev.json diff --git a/.changeset/olive-dogs-rush.md b/.changeset/olive-dogs-rush.md new file mode 100644 index 0000000..7807e8c --- /dev/null +++ b/.changeset/olive-dogs-rush.md @@ -0,0 +1,11 @@ +--- +"agentcommercekit": patch +"@agentcommercekit/ack-pay": patch +"@agentcommercekit/ack-id": patch +"@agentcommercekit/keys": patch +"@agentcommercekit/did": patch +"@agentcommercekit/jwt": patch +"@agentcommercekit/vc": patch +--- + +Add did:pkh support for more chains, including solana diff --git a/demos/e2e/src/agent.ts b/demos/e2e/src/agent.ts index 6082a10..5fb9798 100644 --- a/demos/e2e/src/agent.ts +++ b/demos/e2e/src/agent.ts @@ -79,7 +79,6 @@ export class Agent { this.preferredChainId = preferredChainId const { did: walletDid, didDocument: walletDidDocument } = createDidPkhDocument({ - keypair: this.wallet, address: this.walletAddress, chainId: this.preferredChainId }) diff --git a/demos/e2e/src/index.ts b/demos/e2e/src/index.ts index fb6d033..71109ef 100644 --- a/demos/e2e/src/index.ts +++ b/demos/e2e/src/index.ts @@ -8,8 +8,8 @@ import { waitForEnter } from "@repo/cli-tools" import { + caip2ChainIds, createJwt, - didPkhChainIds, getDidResolver, parseJwtCredential } from "agentcommercekit" @@ -30,7 +30,7 @@ import type { Keypair, PaymentRequest } from "agentcommercekit" * * @see {@link https://namespaces.chainagnostic.org} */ -const CHAIN_ID = didPkhChainIds.evm.baseSepolia +const CHAIN_ID = caip2ChainIds.baseSepolia /** * These are defined outside the `main` function because they are diff --git a/demos/e2e/src/user.ts b/demos/e2e/src/user.ts index 985e15e..8b911cc 100644 --- a/demos/e2e/src/user.ts +++ b/demos/e2e/src/user.ts @@ -54,8 +54,7 @@ export class User { const address = publicKeyToAddress(wallet.publicKey) const { did, didDocument } = createDidPkhDocument({ address, - chainId, - keypair: wallet + chainId }) return new User({ diff --git a/demos/payments/src/constants.ts b/demos/payments/src/constants.ts index 4d19fc5..7665cff 100644 --- a/demos/payments/src/constants.ts +++ b/demos/payments/src/constants.ts @@ -1,5 +1,5 @@ import path from "node:path" -import { didPkhChainIds } from "agentcommercekit" +import { caip2ChainIds } from "agentcommercekit" import { createPublicClient, http } from "viem" import { baseSepolia } from "viem/chains" @@ -20,7 +20,7 @@ export const envFilePath = path.resolve(currentDir, "..", ".env") * Configure the EVM chain you'd like to use: */ export const chain = baseSepolia -export const chainId = didPkhChainIds.evm.baseSepolia +export const chainId = caip2ChainIds.baseSepolia export const usdcAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e" export const publicClient = createPublicClient({ chain, diff --git a/demos/payments/src/utils/keypair-info.ts b/demos/payments/src/utils/keypair-info.ts index ac0199a..d49571d 100644 --- a/demos/payments/src/utils/keypair-info.ts +++ b/demos/payments/src/utils/keypair-info.ts @@ -39,7 +39,7 @@ export async function getKeypairInfo( const account = privateKeyToAccount( `0x${bytesToHexString(keypair.privateKey)}` ) - const did = createDidPkhUri(address, chainId) + const did = createDidPkhUri(chainId, address) const jwtSigner = createJwtSigner(keypair) return { diff --git a/packages/ack-pay/src/verify-payment-receipt.test.ts b/packages/ack-pay/src/verify-payment-receipt.test.ts index e4cff5b..ddbbf66 100644 --- a/packages/ack-pay/src/verify-payment-receipt.test.ts +++ b/packages/ack-pay/src/verify-payment-receipt.test.ts @@ -60,8 +60,8 @@ describe("verifyPaymentReceipt()", () => { paymentOptionId: paymentRequest.paymentOptions[0].id, issuer: receiptIssuerDid, payerDid: createDidPkhUri( - "0x7B3D8F2E1C9A4B5D6E7F8A9B0C1D2E3F4A5B6C", - "eip155:84532" + "eip155:84532", + "0x7B3D8F2E1C9A4B5D6E7F8A9B0C1D2E3F4A5B6C" ) }) diff --git a/packages/did/README.md b/packages/did/README.md index 73aba0a..88e2ab6 100644 --- a/packages/did/README.md +++ b/packages/did/README.md @@ -62,8 +62,8 @@ const keyDid = createDidKeyUri(keypair) // Create a did:pkh URI from an address and chain ID const pkhDid = createDidPkhUri( - "0x1234567890123456789012345678901234567890", - "eip155:1" + "eip155:1", + "0x1234567890123456789012345678901234567890" ) // did:pkh:eip155:1:0x1234567890123456789012345678901234567890 ``` @@ -103,7 +103,7 @@ const { did, didDocument } = createDidWebDocumentFromKeypair({ - `createDidWebUri(input: string | URL): DidWebUri` - Create a did:web URI from a domain or URL - `createDidKeyUri(keypair: Keypair): DidKeyUri` - Create a did:key URI from a keypair -- `createDidPkhUri(address: string, chainId: DidPkhChainId): DidPkhUri` - Create a did:pkh URI from an address and chain ID +- `createDidPkhUri(chainId: DidPkhChainId, address: string): DidPkhUri` - Create a did:pkh URI from an address and chain ID ### Resolution diff --git a/packages/did/package.json b/packages/did/package.json index 3a8954a..cef659d 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -58,7 +58,6 @@ "@agentcommercekit/keys": "workspace:*", "did-resolver": "^4.1.0", "key-did-resolver": "^4.0.0", - "pkh-did-resolver": "^2.0.0", "valibot": "^1.1.0", "varint": "^6.0.0" }, diff --git a/packages/did/src/caip/caip.test.ts b/packages/did/src/caip/caip.test.ts index 4b95ccb..cdfaaa8 100644 --- a/packages/did/src/caip/caip.test.ts +++ b/packages/did/src/caip/caip.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest" -import { isCaip2ChainId } from "./caip" +import { createCaip10AccountId, isCaip2ChainId } from "./caip" describe("isCaip2ChainId", () => { it("returns true for valid CAIP-2 chain IDs", () => { @@ -31,3 +31,23 @@ describe("isCaip2ChainId", () => { expect(isCaip2ChainId("eip-155:1")).toBe(false) // hyphen not allowed in namespace }) }) + +describe("createCaip10AccountId", () => { + it("creates a caip 10 account ID for EVM address", () => { + const result = createCaip10AccountId( + "eip155:1", + "0x1234567890123456789012345678901234567890" + ) + expect(result).toBe("eip155:1:0x1234567890123456789012345678901234567890") + }) + + it("creates a caip 10 account ID for Solana address", () => { + const result = createCaip10AccountId( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + expect(result).toBe( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + }) +}) diff --git a/packages/did/src/caip/caip.ts b/packages/did/src/caip/caip.ts index ede88b3..b72d4e3 100644 --- a/packages/did/src/caip/caip.ts +++ b/packages/did/src/caip/caip.ts @@ -1,6 +1,22 @@ import * as v from "valibot" -import { caip2ChainIdSchema } from "./schemas/valibot" -import type { Caip2ChainId } from "./types" +import { caip10AccountIdSchema, caip2ChainIdSchema } from "./schemas/valibot" +import type { Caip10AccountId, Caip2ChainId } from "./types" + +/** + * A set of CAIP-2 chain IDs for select networks + * + * @see {@link https://chainagnostic.org/CAIPs/caip-2} + */ +export const caip2ChainIds = { + ethereumMainnet: "eip155:1", + ethereumSepolia: "eip155:11155111", + baseMainnet: "eip155:8453", + baseSepolia: "eip155:84532", + arbitrumMainnet: "eip155:42161", + arbitrumSepolia: "eip155:421614", + solanaMainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + solanaDevnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" +} as const /** * Checks if a given string is a valid CAIP-2 chain ID (`namespace:reference`) @@ -14,3 +30,51 @@ import type { Caip2ChainId } from "./types" export function isCaip2ChainId(chainId: unknown): chainId is Caip2ChainId { return v.is(caip2ChainIdSchema, chainId) } + +/** + * Checks if a given string is a valid CAIP-10 account ID + */ +export function isCaip10AccountId( + accountId: unknown +): accountId is Caip10AccountId { + return v.is(caip10AccountIdSchema, accountId) +} + +/** + * Create a CAIP-10 Account ID + * + * @param address - The address to create the CAIP-10 Account ID for + * @param chainId - The CAIP-2 chain ID (e.g. `eip155:1`, `solana`) for this address + * @returns The CAIP-10 Account ID + */ +export function createCaip10AccountId( + chainId: Caip2ChainId, + address: string +): Caip10AccountId { + return `${chainId}:${address}` +} + +interface Caip2Parts { + namespace: string + reference: string +} + +interface Caip10Parts extends Caip2Parts { + accountId: string +} + +export function caip2Parts(caip: Caip2ChainId): Caip2Parts { + const [namespace, reference] = caip.split(":") + if (!namespace || !reference) { + throw new Error("Invalid CAIP-2 chain ID") + } + return { namespace, reference } +} + +export function caip10Parts(caip: Caip10AccountId): Caip10Parts { + const [namespace, reference, accountId] = caip.split(":") + if (!namespace || !reference || !accountId) { + throw new Error("Invalid CAIP-10 account ID") + } + return { namespace, reference, accountId } +} diff --git a/packages/did/src/create-did-document.ts b/packages/did/src/create-did-document.ts index dc5cfb3..a308e03 100644 --- a/packages/did/src/create-did-document.ts +++ b/packages/did/src/create-did-document.ts @@ -84,16 +84,11 @@ function convertLegacyPublicKeyToMultibase( /** * Base options for creating a DID document */ -export interface CreateDidDocumentOptions { +export type CreateDidDocumentOptions = { /** * The DID to include in the DID document */ did: DidUri - /** - * The public key to include in the DID document - */ - publicKey: PublicKeyWithEncoding - /** * Additional URIs that are equivalent to this DID */ @@ -111,16 +106,70 @@ export interface CreateDidDocumentOptions { */ additionalContexts?: string[] /** - * Optional verification method to use instead of building one + * CapabilityDelegation verification method */ - verificationMethod?: VerificationMethod -} + capabilityDelegation?: string[] + /** + * CapabilityInvocation verification method + */ + capabilityInvocation?: string[] +} & ( + | { + /** + * The public key to include in the DID document + */ + publicKey: PublicKeyWithEncoding + /** + * Optional verification method to use instead of building one + */ + verificationMethod?: never + } + | { + /** + * The public key to include in the DID document (not required when verificationMethod is provided) + */ + publicKey?: never + /** + * Verification method to use instead of building one from publicKey + */ + verificationMethod: VerificationMethod + } +) /** * Create a DID document from a public key * * @param options - The {@link CreateDidDocumentOptions} to use * @returns A {@link DidDocument} + * + * @example + * ```ts + * const document = createDidDocument({ + * did: "did:example:123", + * publicKey: { + * encoding: "jwk", + * curve: "Ed25519", + * value: { + * kty: "OKP", + * crv: "Ed25519", + * x: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + * } + * } + * }) + * ``` + * + * @example + * ```ts + * const document2 = createDidDocument({ + * did: "did:example:123", + * verificationMethod: { + * id: "did:example:123#key-1", + * type: "Multikey", + * controller: "did:example:123", + * publicKeyMultibase: "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" + * } + * }) + * ``` */ export function createDidDocument({ did, @@ -129,42 +178,45 @@ export function createDidDocument({ alsoKnownAs, service, additionalContexts, - verificationMethod + verificationMethod, + capabilityDelegation, + capabilityInvocation }: CreateDidDocumentOptions): DidDocument { - verificationMethod ??= createVerificationMethod({ - did, - publicKey - }) - - const verificationMethodContext = - verificationMethod.type === "Multikey" - ? "https://w3id.org/security/multikey/v1" - : "https://w3id.org/security/jwk/v1" + if (!verificationMethod) { + // If no verification method is provided, we need to create one from the + // public key + if (!publicKey) { + throw new Error("Either publicKey or verificationMethod must be provided") + } + + verificationMethod ??= createVerificationMethod({ + did, + publicKey + }) + } - const contexts = [ - "https://www.w3.org/ns/did/v1", - verificationMethodContext, - ...(additionalContexts ?? []) - ] + const contexts = ["https://www.w3.org/ns/did/v1"] + if (verificationMethod.type === "Multikey") { + contexts.push("https://w3id.org/security/multikey/v1") + } else if ( + verificationMethod.type === "JsonWebKey2020" || + verificationMethod.type === "JsonWebKey" + ) { + contexts.push("https://w3id.org/security/jwk/v1") + } + contexts.push(...(additionalContexts ?? [])) const document: DidDocument = { "@context": contexts, id: did, verificationMethod: [verificationMethod], authentication: [verificationMethod.id], - assertionMethod: [verificationMethod.id] - } - - if (controller) { - document.controller = controller - } - - if (alsoKnownAs) { - document.alsoKnownAs = alsoKnownAs - } - - if (service) { - document.service = service + assertionMethod: [verificationMethod.id], + ...(controller && { controller }), + ...(alsoKnownAs && { alsoKnownAs }), + ...(service && { service }), + ...(capabilityDelegation && { capabilityDelegation }), + ...(capabilityInvocation && { capabilityInvocation }) } return document @@ -172,7 +224,7 @@ export function createDidDocument({ export type CreateDidDocumentFromKeypairOptions = Omit< CreateDidDocumentOptions, - "publicKey" + "publicKey" | "verificationMethod" > & { /** * The keypair to create the did document from diff --git a/packages/did/src/did-resolvers/get-did-resolver.ts b/packages/did/src/did-resolvers/get-did-resolver.ts index 55621c6..98d8580 100644 --- a/packages/did/src/did-resolvers/get-did-resolver.ts +++ b/packages/did/src/did-resolvers/get-did-resolver.ts @@ -1,6 +1,6 @@ import { getResolver as getKeyDidResolver } from "key-did-resolver" -import { getResolver as getPkhDidResolver } from "pkh-did-resolver" import { DidResolver } from "./did-resolver" +import { getResolver as getPkhDidResolver } from "./pkh-did-resolver" import { getResolver as getWebDidResolver } from "./web-did-resolver" import type { DidWebResolverOptions } from "./web-did-resolver" import type { ResolverOptions } from "did-resolver" diff --git a/packages/did/src/did-resolvers/pkh-did-resolver.test.ts b/packages/did/src/did-resolvers/pkh-did-resolver.test.ts new file mode 100644 index 0000000..9790e8c --- /dev/null +++ b/packages/did/src/did-resolvers/pkh-did-resolver.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest" +import { resolve } from "./pkh-did-resolver" +import fixtureEthereumMainnet from "../../test-fixtures/did-pkh/did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a.json" +import fixtureBaseSepolia from "../../test-fixtures/did-pkh/did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011.json" +import fixtureSolana from "../../test-fixtures/did-pkh/did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev.json" + +/** + * Test vectors from the did-pkh spec + * @see {@link https://github.com/w3c-ccg/did-pkh/tree/main/test-vectors} + */ +const fixtures = { + ethereumMainnet: { + did: "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a", + fixture: fixtureEthereumMainnet + }, + baseSepolia: { + did: "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011", + fixture: fixtureBaseSepolia + }, + solana: { + did: "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev", + fixture: fixtureSolana + } +} + +describe("pkh-did-resolver", () => { + it.each(Object.entries(fixtures))( + "resolves %s did:pkh URIs", + async (_, { did, fixture }) => { + const result = await resolve(did) + + expect(result.didDocument).toEqual(fixture) + } + ) + + it("returns an error for unknown did:pkh URIs", async () => { + const result = await resolve("did:pkh:eip-155:1:invalid") + + expect(result).toEqual({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { error: "invalidDid" } + }) + }) +}) diff --git a/packages/did/src/did-resolvers/pkh-did-resolver.ts b/packages/did/src/did-resolvers/pkh-did-resolver.ts new file mode 100644 index 0000000..2712b62 --- /dev/null +++ b/packages/did/src/did-resolvers/pkh-did-resolver.ts @@ -0,0 +1,38 @@ +/** + * A `did:pkh` resolver for use with `did-resolver` + */ +import { + caip10AccountIdFromDidPkhUri, + createDidPkhDocumentFromCaip10AccountId, + isDidPkhUri +} from "../methods/did-pkh" +import type { DIDResolutionResult, DIDResolver } from "did-resolver" + +export async function resolve(did: string): Promise { + if (!isDidPkhUri(did)) { + return { + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { error: "invalidDid" } + } + } + + const caip10AccountId = caip10AccountIdFromDidPkhUri(did) + const { didDocument } = + createDidPkhDocumentFromCaip10AccountId(caip10AccountId) + + return Promise.resolve({ + didDocument, + didDocumentMetadata: {}, + didResolutionMetadata: { contentType: "application/did+ld+json" } + }) +} + +/** + * Get a resolver for did:pkh + * + * @returns A resolver for did:pkh + */ +export function getResolver(): { pkh: DIDResolver } { + return { pkh: resolve } +} diff --git a/packages/did/src/methods/did-pkh.test.ts b/packages/did/src/methods/did-pkh.test.ts index a5d3dce..11693d1 100644 --- a/packages/did/src/methods/did-pkh.test.ts +++ b/packages/did/src/methods/did-pkh.test.ts @@ -1,14 +1,11 @@ import { describe, expect, it } from "vitest" import { addressFromDidPkhUri, - createBlockchainAccountId, createDidPkhDocument, createDidPkhUri, - didPkhChainIds, didPkhParts, isDidPkhUri } from "./did-pkh" -import type { Keypair } from "@agentcommercekit/keys" describe("didPkhParts", () => { it("parses valid did:pkh URIs correctly", () => { @@ -123,51 +120,11 @@ describe("addressFromDidPkhUri", () => { }) }) -describe("didPkhChainIds", () => { - it("contains expected EVM chain IDs", () => { - expect(didPkhChainIds.evm.mainnet).toBe("eip155:1") - expect(didPkhChainIds.evm.sepolia).toBe("eip155:11155111") - expect(didPkhChainIds.evm.base).toBe("eip155:8453") - expect(didPkhChainIds.evm.baseSepolia).toBe("eip155:84532") - expect(didPkhChainIds.evm.arbitrum).toBe("eip155:42161") - expect(didPkhChainIds.evm.arbitrumSepolia).toBe("eip155:421614") - }) - - it("contains expected SVM chain IDs", () => { - expect(didPkhChainIds.svm.mainnet).toBe( - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - ) - expect(didPkhChainIds.svm.devnet).toBe( - "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" - ) - }) -}) - -describe("createBlockchainAccountId", () => { - it("creates blockchain account ID for EVM address", () => { - const result = createBlockchainAccountId( - "0x1234567890123456789012345678901234567890", - "eip155:1" - ) - expect(result).toBe("eip155:1:0x1234567890123456789012345678901234567890") - }) - - it("creates blockchain account ID for Solana address", () => { - const result = createBlockchainAccountId( - "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - ) - expect(result).toBe( - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" - ) - }) -}) - describe("createDidPkhUri", () => { it("creates did:pkh URI for EVM address", () => { const result = createDidPkhUri( - "0x1234567890123456789012345678901234567890", - "eip155:1" + "eip155:1", + "0x1234567890123456789012345678901234567890" ) expect(result).toBe( "did:pkh:eip155:1:0x1234567890123456789012345678901234567890" @@ -176,8 +133,8 @@ describe("createDidPkhUri", () => { it("creates did:pkh URI for Solana address", () => { const result = createDidPkhUri( - "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" ) expect(result).toBe( "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" @@ -186,21 +143,8 @@ describe("createDidPkhUri", () => { }) describe("createDidPkhDocument", () => { - const mockSecp256k1Keypair: Keypair = { - curve: "secp256k1", - publicKey: new Uint8Array([1, 2, 3, 4]), - privateKey: new Uint8Array([5, 6, 7, 8]) - } - - const mockEd25519Keypair: Keypair = { - curve: "Ed25519", - publicKey: new Uint8Array([1, 2, 3, 4]), - privateKey: new Uint8Array([5, 6, 7, 8]) - } - it("creates did:pkh document for EVM address with secp256k1 keypair", () => { const result = createDidPkhDocument({ - keypair: mockSecp256k1Keypair, address: "0x1234567890123456789012345678901234567890", chainId: "eip155:1" }) @@ -225,7 +169,6 @@ describe("createDidPkhDocument", () => { it("creates did:pkh document for Solana address with Ed25519 keypair", () => { const result = createDidPkhDocument({ - keypair: mockEd25519Keypair, address: "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", chainId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" }) @@ -249,7 +192,6 @@ describe("createDidPkhDocument", () => { it("creates did:pkh document with custom controller", () => { const result = createDidPkhDocument({ - keypair: mockSecp256k1Keypair, address: "0x1234567890123456789012345678901234567890", chainId: "eip155:1", controller: "did:key:zQ3sharFd8K3z6L9b5X5J7m8n9o0p1q2r3s4t5u6v7w8x9y0z" @@ -259,26 +201,4 @@ describe("createDidPkhDocument", () => { "did:key:zQ3sharFd8K3z6L9b5X5J7m8n9o0p1q2r3s4t5u6v7w8x9y0z" ) }) - - it("throws error for mismatched keypair algorithm and chain", () => { - expect(() => - createDidPkhDocument({ - keypair: mockEd25519Keypair, - address: "0x1234567890123456789012345678901234567890", - chainId: "eip155:1" - }) - ).toThrow( - "Invalid keypair algorithm. Expected secp256k1 for chain eip155:1" - ) - - expect(() => - createDidPkhDocument({ - keypair: mockSecp256k1Keypair, - address: "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P", - chainId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - }) - ).toThrow( - "Invalid keypair algorithm. Expected Ed25519 for chain solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - ) - }) }) diff --git a/packages/did/src/methods/did-pkh.ts b/packages/did/src/methods/did-pkh.ts index 97015fa..3bb455c 100644 --- a/packages/did/src/methods/did-pkh.ts +++ b/packages/did/src/methods/did-pkh.ts @@ -1,10 +1,20 @@ /* eslint-disable @cspell/spellchecker */ -import { isCaip2ChainId } from "../caip/caip" -import { createDidDocumentFromKeypair } from "../create-did-document" +import { + base58ToBytes, + isBase58, + publicKeyBytesToJwk +} from "@agentcommercekit/keys/encoding" +import { + caip10Parts, + createCaip10AccountId, + isCaip10AccountId, + isCaip2ChainId +} from "../caip/caip" +import { createDidDocument } from "../create-did-document" import type { DidUri } from "../did-uri" import type { Caip10AccountId, Caip2ChainId } from "../schemas/valibot" import type { DidUriWithDocument } from "../types" -import type { Keypair } from "@agentcommercekit/keys" +import type { VerificationMethod } from "did-resolver" /** * Methods for creating and verifying did:pkh documents @@ -104,38 +114,24 @@ export function addressFromDidPkhUri(didUri: string): string { return address } -/** - * The CAIP-2 chain ID for select networks - * - * @see {@link https://chainagnostic.org/CAIPs/caip-2} - */ -export const didPkhChainIds = { - evm: { - mainnet: "eip155:1", - sepolia: "eip155:11155111", - base: "eip155:8453", - baseSepolia: "eip155:84532", - arbitrum: "eip155:42161", - arbitrumSepolia: "eip155:421614" - }, - svm: { - mainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - devnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" +export function caip10AccountIdFromDidPkhUri( + didUri: DidPkhUri +): Caip10AccountId { + const caip10AccountId = didUri.replace("did:pkh:", "") + if (!isCaip10AccountId(caip10AccountId)) { + throw new Error("Invalid did:pkh URI") } -} as const + return caip10AccountId +} /** - * Create a blockchain account ID - * - * @param address - The address to create the blockchain account ID for - * @param chainId - The CAIP-2 chain ID (e.g. `eip155:1`, `solana`) for this address - * @returns The blockchain account ID + * @deprecated Use `createCaip10AccountId` instead */ export function createBlockchainAccountId( address: string, chainId: Caip2ChainId -): `${Caip2ChainId}:${string}` { - return `${chainId}:${address}` +) { + return createCaip10AccountId(chainId, address) } /** @@ -155,14 +151,69 @@ export function createBlockchainAccountId( * ``` */ export function createDidPkhUri( - address: string, - chainId: Caip2ChainId + chainId: Caip2ChainId, + address: string ): DidUri { - return `did:pkh:${createBlockchainAccountId(address, chainId)}` + return `did:pkh:${createCaip10AccountId(chainId, address)}` +} + +/** + * Create a did:pkh URI from a CAIP-10 account ID + */ +export function createDidPkhUriFromCaip10AccountId( + caip10AccountId: Caip10AccountId +): DidUri { + return `did:pkh:${caip10AccountId}` +} + +const jsonLdContexts = { + Ed25519VerificationKey2020: [ + "https://w3id.org/security#blockchainAccountId", + "https://w3id.org/security#publicKeyJwk", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + EcdsaSecp256k1RecoveryMethod2020: [ + "https://w3id.org/security#blockchainAccountId", + "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020" + ] +} + +/** + * Create a did:pkh document from a CAIP-10 account ID + * + * @example + * ```ts + * const didDocument = createDidPkhDocumentFromCaip10AccountId( + * "eip155:1:0x1234567890123456789012345678901234567890", + * "did:example:123" + * ) + * ``` + * + * @param caip10AccountId - The CAIP-10 account ID + * @param controller - The controller of the did:pkh document + * @returns The did:pkh document + */ +export function createDidPkhDocumentFromCaip10AccountId( + caip10AccountId: Caip10AccountId, + controller?: DidUri +): DidUriWithDocument { + const did = createDidPkhUriFromCaip10AccountId(caip10AccountId) + const verificationMethod = createVerificationMethod(did, caip10AccountId) + const additionalContexts = jsonLdContexts[verificationMethod.type] + + const didDocument = createDidDocument({ + did, + controller, + additionalContexts, + verificationMethod, + capabilityDelegation: [verificationMethod.id], + capabilityInvocation: [verificationMethod.id] + }) + + return { did, didDocument } } interface CreateDidPkhDocumentOptions { - keypair: Keypair address: string chainId: Caip2ChainId controller?: DidUri @@ -170,70 +221,50 @@ interface CreateDidPkhDocumentOptions { /** * Create a did:pkh document - * - * @param keypair - The keypair to create the did:pkh document for - * @param chainId - The CAIP-2 chain ID (e.g. `eip155:1`, `solana`) for this address - * @param controller - The controller of the did:pkh document * @returns The did:pkh document */ export function createDidPkhDocument({ - keypair, address, chainId, controller }: CreateDidPkhDocumentOptions): DidUriWithDocument { - // Validate that the keypair algorithm matches the chain - const algorithm = chainId.startsWith("solana") ? "Ed25519" : "secp256k1" - if (keypair.curve !== algorithm) { - throw new Error( - `Invalid keypair algorithm. Expected ${algorithm} for chain ${chainId}` - ) - } - - const blockchainAccountId = createBlockchainAccountId(address, chainId) - const did = createDidPkhUri(address, chainId) - - const additionalContexts = - algorithm === "secp256k1" - ? [ - "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020", - "https://w3id.org/security#blockchainAccountId" - ] - : [ - "https://w3id.org/security#publicKeyJwk", - "https://w3id.org/security#blockchainAccountId" - ] - - if (algorithm === "secp256k1") { - const didDocument = createDidDocumentFromKeypair({ - did, - keypair, - controller, - encoding: "hex", - additionalContexts, - verificationMethod: { - id: `${did}#blockchainAccountId`, - type: "EcdsaSecp256k1RecoveryMethod2020", - controller: did, - blockchainAccountId - } - }) - - return { did, didDocument } - } + const caip10AccountId = createCaip10AccountId(chainId, address) + return createDidPkhDocumentFromCaip10AccountId(caip10AccountId, controller) +} - const didDocument = createDidDocumentFromKeypair({ - did, - keypair, - controller, - encoding: "jwk", - additionalContexts - }) +/** + * Create a verification method for a did:pkh document + * @param did - The did:pkh URI + * @param caip10AccountId - The CAIP-10 account ID + * @returns The verification method + */ +function createVerificationMethod( + did: DidUri, + caip10AccountId: Caip10AccountId +): Omit & { + type: "Ed25519VerificationKey2020" | "EcdsaSecp256k1RecoveryMethod2020" +} { + const { namespace, accountId } = caip10Parts(caip10AccountId) - // Add blockchain account ID to the verification method - if (didDocument.verificationMethod?.[0]) { - didDocument.verificationMethod[0].blockchainAccountId = blockchainAccountId + if (namespace.startsWith("solana") && isBase58(accountId)) { + const publicKeyJwk = publicKeyBytesToJwk( + base58ToBytes(accountId), + "Ed25519" + ) + return { + id: `${did}#controller`, + type: "Ed25519VerificationKey2020", + controller: did, + blockchainAccountId: caip10AccountId, + publicKeyJwk + } } - return { did, didDocument } + // Fall back to secp256k1 + return { + id: `${did}#blockchainAccountId`, + type: "EcdsaSecp256k1RecoveryMethod2020", + controller: did, + blockchainAccountId: caip10AccountId + } } diff --git a/packages/did/test-fixtures/did-pkh/did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a.json b/packages/did/test-fixtures/did-pkh/did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a.json new file mode 100644 index 0000000..ddc43ca --- /dev/null +++ b/packages/did/test-fixtures/did-pkh/did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security#blockchainAccountId", + "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020" + ], + "id": "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a", + "verificationMethod": [ + { + "id": "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a#blockchainAccountId", + "type": "EcdsaSecp256k1RecoveryMethod2020", + "controller": "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a", + "blockchainAccountId": "eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a" + } + ], + "authentication": [ + "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a#blockchainAccountId" + ], + "assertionMethod": [ + "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a#blockchainAccountId" + ], + "capabilityDelegation": [ + "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a#blockchainAccountId" + ], + "capabilityInvocation": [ + "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a#blockchainAccountId" + ] +} diff --git a/packages/did/test-fixtures/did-pkh/did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011.json b/packages/did/test-fixtures/did-pkh/did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011.json new file mode 100644 index 0000000..6d409ae --- /dev/null +++ b/packages/did/test-fixtures/did-pkh/did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security#blockchainAccountId", + "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020" + ], + "id": "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011", + "verificationMethod": [ + { + "id": "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011#blockchainAccountId", + "type": "EcdsaSecp256k1RecoveryMethod2020", + "controller": "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011", + "blockchainAccountId": "eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011" + } + ], + "authentication": [ + "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011#blockchainAccountId" + ], + "assertionMethod": [ + "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011#blockchainAccountId" + ], + "capabilityDelegation": [ + "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011#blockchainAccountId" + ], + "capabilityInvocation": [ + "did:pkh:eip155:84532:0xa0ae58da58dfa46fa55c3b86545e7065f90ff011#blockchainAccountId" + ] +} diff --git a/packages/did/test-fixtures/did-pkh/did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev.json b/packages/did/test-fixtures/did-pkh/did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev.json new file mode 100644 index 0000000..a29a75a --- /dev/null +++ b/packages/did/test-fixtures/did-pkh/did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security#blockchainAccountId", + "https://w3id.org/security#publicKeyJwk", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev", + "verificationMethod": [ + { + "id": "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev#controller", + "type": "Ed25519VerificationKey2020", + "controller": "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev", + "blockchainAccountId": "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "qDkywhH-S6nNxQhA6SHKsoFW7A2gX-X0b3TtwVBMHm8" + } + } + ], + "authentication": [ + "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev#controller" + ], + "assertionMethod": [ + "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev#controller" + ], + "capabilityDelegation": [ + "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev#controller" + ], + "capabilityInvocation": [ + "did:pkh:solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ:CKg5d12Jhpej1JqtmxLJgaFqqeYjxgPqToJ4LBdvG9Ev#controller" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4dd4cc..0c75c2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -559,9 +559,6 @@ importers: key-did-resolver: specifier: ^4.0.0 version: 4.0.0 - pkh-did-resolver: - specifier: ^2.0.0 - version: 2.0.0 valibot: specifier: ^1.1.0 version: 1.1.0(typescript@5.8.3) @@ -2875,9 +2872,6 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} - caip@1.1.1: - resolution: {integrity: sha512-a3v5lteUUOoyRI0U6qe5ayCCGkF2mCmJ5zQMDnOD2vRjgRg6sm9p8TsRC2h4D4beyqRN9RYniphAPnj/+jQC6g==} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -5144,10 +5138,6 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pkh-did-resolver@2.0.0: - resolution: {integrity: sha512-OspfuI99LxLQ02/+tynJjQYEVmagSxKZIZQwv+54SNYWYQSI2dSZGOQmN2l3a2IptUj7AZ8Z7WzZPu+XY4siXQ==} - engines: {node: '>=14.14'} - pony-cause@1.1.1: resolution: {integrity: sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==} engines: {node: '>=12.0.0'} @@ -8681,8 +8671,6 @@ snapshots: normalize-url: 8.0.1 responselike: 3.0.0 - caip@1.1.1: {} - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -11405,10 +11393,6 @@ snapshots: pify@4.0.1: {} - pkh-did-resolver@2.0.0: - dependencies: - caip: 1.1.1 - pony-cause@1.1.1: {} possible-typed-array-names@1.1.0: {} From e7af75be1e89408726b5e4f4f4bdf6879259e357 Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Fri, 25 Jul 2025 23:24:35 -0400 Subject: [PATCH 4/6] Move CAIPs to own package @agentcommercekit/caip --- packages/agentcommercekit/package.json | 1 + packages/agentcommercekit/src/index.ts | 1 + .../agentcommercekit/src/schemas/valibot.ts | 1 + .../agentcommercekit/src/schemas/zod/v3.ts | 1 + .../agentcommercekit/src/schemas/zod/v4.ts | 1 + packages/caip/README.md | 17 +++ packages/caip/eslint.config.js | 7 ++ packages/caip/package.json | 80 +++++++++++++ packages/caip/src/caips/caip-10.test.ts | 22 ++++ packages/caip/src/caips/caip-10.ts | 46 ++++++++ packages/caip/src/caips/caip-19.ts | 37 ++++++ packages/caip/src/caips/caip-2.ts | 49 ++++++++ packages/caip/src/caips/index.ts | 3 + packages/caip/src/index.ts | 2 + .../caip => caip/src/schemas}/schemas.test.ts | 12 +- packages/caip/src/schemas/valibot.ts | 71 ++++++++++++ packages/caip/src/schemas/zod/v3.ts | 66 +++++++++++ packages/caip/src/schemas/zod/v4.ts | 66 +++++++++++ packages/caip/tsconfig.json | 5 + packages/caip/tsdown.config.ts | 12 ++ packages/caip/vitest.config.ts | 9 ++ packages/caip/vitest.setup.ts | 13 +++ packages/did/package.json | 1 + packages/did/src/caip/caip.test.ts | 53 --------- packages/did/src/caip/caip.ts | 80 ------------- packages/did/src/caip/index.ts | 2 - packages/did/src/caip/schemas/core.ts | 105 ------------------ packages/did/src/caip/schemas/valibot.ts | 48 -------- packages/did/src/caip/schemas/zod/v3.ts | 28 ----- packages/did/src/caip/schemas/zod/v4.ts | 28 ----- packages/did/src/caip/types.ts | 7 -- packages/did/src/index.ts | 1 - packages/did/src/methods/did-key.test.ts | 1 - packages/did/src/methods/did-pkh.ts | 13 +-- packages/did/src/schemas/valibot.ts | 4 +- packages/did/src/schemas/zod/v3.ts | 4 +- packages/did/src/schemas/zod/v4.ts | 4 +- packages/jwt/src/jwt-string.test.ts | 1 - pnpm-lock.yaml | 36 ++++++ tools/eslint-config/base.js | 1 + 40 files changed, 564 insertions(+), 375 deletions(-) create mode 100644 packages/caip/README.md create mode 100644 packages/caip/eslint.config.js create mode 100644 packages/caip/package.json create mode 100644 packages/caip/src/caips/caip-10.test.ts create mode 100644 packages/caip/src/caips/caip-10.ts create mode 100644 packages/caip/src/caips/caip-19.ts create mode 100644 packages/caip/src/caips/caip-2.ts create mode 100644 packages/caip/src/caips/index.ts create mode 100644 packages/caip/src/index.ts rename packages/{did/src/caip => caip/src/schemas}/schemas.test.ts (94%) create mode 100644 packages/caip/src/schemas/valibot.ts create mode 100644 packages/caip/src/schemas/zod/v3.ts create mode 100644 packages/caip/src/schemas/zod/v4.ts create mode 100644 packages/caip/tsconfig.json create mode 100644 packages/caip/tsdown.config.ts create mode 100644 packages/caip/vitest.config.ts create mode 100644 packages/caip/vitest.setup.ts delete mode 100644 packages/did/src/caip/caip.test.ts delete mode 100644 packages/did/src/caip/caip.ts delete mode 100644 packages/did/src/caip/index.ts delete mode 100644 packages/did/src/caip/schemas/core.ts delete mode 100644 packages/did/src/caip/schemas/valibot.ts delete mode 100644 packages/did/src/caip/schemas/zod/v3.ts delete mode 100644 packages/did/src/caip/schemas/zod/v4.ts delete mode 100644 packages/did/src/caip/types.ts diff --git a/packages/agentcommercekit/package.json b/packages/agentcommercekit/package.json index 957badb..dbdd005 100644 --- a/packages/agentcommercekit/package.json +++ b/packages/agentcommercekit/package.json @@ -77,6 +77,7 @@ "dependencies": { "@agentcommercekit/ack-id": "workspace:*", "@agentcommercekit/ack-pay": "workspace:*", + "@agentcommercekit/caip": "workspace:*", "@agentcommercekit/did": "workspace:*", "@agentcommercekit/jwt": "workspace:*", "@agentcommercekit/keys": "workspace:*", diff --git a/packages/agentcommercekit/src/index.ts b/packages/agentcommercekit/src/index.ts index 7d6cfec..0037ccd 100644 --- a/packages/agentcommercekit/src/index.ts +++ b/packages/agentcommercekit/src/index.ts @@ -1,5 +1,6 @@ export * from "@agentcommercekit/ack-pay" export * from "@agentcommercekit/ack-id" +export * from "@agentcommercekit/caip" export * from "@agentcommercekit/did" export * from "@agentcommercekit/jwt" export * from "@agentcommercekit/keys" diff --git a/packages/agentcommercekit/src/schemas/valibot.ts b/packages/agentcommercekit/src/schemas/valibot.ts index bd4d555..da4df2d 100644 --- a/packages/agentcommercekit/src/schemas/valibot.ts +++ b/packages/agentcommercekit/src/schemas/valibot.ts @@ -1,5 +1,6 @@ export * from "@agentcommercekit/ack-pay/schemas/valibot" export * from "@agentcommercekit/ack-id/schemas/valibot" +export * from "@agentcommercekit/caip/schemas/valibot" export * from "@agentcommercekit/did/schemas/valibot" export * from "@agentcommercekit/jwt/schemas/valibot" export * from "@agentcommercekit/vc/schemas/valibot" diff --git a/packages/agentcommercekit/src/schemas/zod/v3.ts b/packages/agentcommercekit/src/schemas/zod/v3.ts index 888eb1d..7da8fa8 100644 --- a/packages/agentcommercekit/src/schemas/zod/v3.ts +++ b/packages/agentcommercekit/src/schemas/zod/v3.ts @@ -1,5 +1,6 @@ export * from "@agentcommercekit/ack-pay/schemas/zod/v3" export * from "@agentcommercekit/ack-id/schemas/zod/v3" +export * from "@agentcommercekit/caip/schemas/zod/v3" export * from "@agentcommercekit/did/schemas/zod/v3" export * from "@agentcommercekit/jwt/schemas/zod/v3" export * from "@agentcommercekit/vc/schemas/zod/v3" diff --git a/packages/agentcommercekit/src/schemas/zod/v4.ts b/packages/agentcommercekit/src/schemas/zod/v4.ts index 5418445..26459b5 100644 --- a/packages/agentcommercekit/src/schemas/zod/v4.ts +++ b/packages/agentcommercekit/src/schemas/zod/v4.ts @@ -1,5 +1,6 @@ export * from "@agentcommercekit/ack-pay/schemas/zod/v4" export * from "@agentcommercekit/ack-id/schemas/zod/v4" +export * from "@agentcommercekit/caip/schemas/zod/v4" export * from "@agentcommercekit/did/schemas/zod/v4" export * from "@agentcommercekit/jwt/schemas/zod/v4" export * from "@agentcommercekit/vc/schemas/zod/v4" diff --git a/packages/caip/README.md b/packages/caip/README.md new file mode 100644 index 0000000..40e24e9 --- /dev/null +++ b/packages/caip/README.md @@ -0,0 +1,17 @@ +# @agentcommercekit/caip + +CAIP (Chain Agnostic Improvement Proposal) utilities with support for CAIP-2, CAIP-10, and CAIP-19. + +This package is part of the [Agent Commerce Kit](https://www.agentcommercekit.com). + +## Installation + +```sh +npm i @agentcommercekit/caip +# or +pnpm add @agentcommercekit/caip +``` + +## License (MIT) + +Copyright (c) 2025 [Catena Labs, Inc](https://catenalabs.com). diff --git a/packages/caip/eslint.config.js b/packages/caip/eslint.config.js new file mode 100644 index 0000000..0979926 --- /dev/null +++ b/packages/caip/eslint.config.js @@ -0,0 +1,7 @@ +// @ts-check + +import { config } from "@repo/eslint-config/base" + +export default config({ + root: import.meta.dirname +}) diff --git a/packages/caip/package.json b/packages/caip/package.json new file mode 100644 index 0000000..cc38abd --- /dev/null +++ b/packages/caip/package.json @@ -0,0 +1,80 @@ +{ + "name": "@agentcommercekit/caip", + "version": "0.1.0", + "homepage": "https://github.com/agentcommercekit/ack#readme", + "bugs": "https://github.com/agentcommercekit/ack/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/agentcommercekit/ack.git", + "directory": "packages/caip" + }, + "license": "MIT", + "author": { + "name": "Catena Labs", + "url": "https://catenalabs.com" + }, + "type": "module", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./schemas/zod/v3": { + "types": "./dist/schemas/zod/v3.d.ts", + "default": "./dist/schemas/zod/v3.js" + }, + "./schemas/zod/v4": { + "types": "./dist/schemas/zod/v4.d.ts", + "default": "./dist/schemas/zod/v4.js" + }, + "./schemas/valibot": { + "types": "./dist/schemas/valibot.d.ts", + "default": "./dist/schemas/valibot.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "package.json", + "LICENSE", + "README.md" + ], + "scripts": { + "build": "tsdown", + "check:types": "tsc --noEmit", + "clean": "git clean -fdX .turbo dist", + "dev": "pnpm build --watch --no-clean", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test": "vitest" + }, + "dependencies": {}, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "eslint": "^9.27.0", + "standard-parse": "^0.3.0", + "tsdown": "^0.11.12", + "typescript": "^5", + "valibot": "^1.1.0", + "vitest": "^3.2.4", + "zod": "^3.25.0" + }, + "peerDependencies": { + "valibot": "^1.1.0", + "zod": "^3.25.0" + }, + "peerDependenciesMeta": { + "valibot": { + "optional": true + }, + "zod": { + "optional": true + } + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/caip/src/caips/caip-10.test.ts b/packages/caip/src/caips/caip-10.test.ts new file mode 100644 index 0000000..0178fa4 --- /dev/null +++ b/packages/caip/src/caips/caip-10.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest" +import { createCaip10AccountId } from "./index" + +describe("createCaip10AccountId", () => { + it("creates a caip 10 account ID for EVM address", () => { + const result = createCaip10AccountId( + "eip155:1", + "0x1234567890123456789012345678901234567890" + ) + expect(result).toBe("eip155:1:0x1234567890123456789012345678901234567890") + }) + + it("creates a caip 10 account ID for Solana address", () => { + const result = createCaip10AccountId( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + expect(result).toBe( + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" + ) + }) +}) diff --git a/packages/caip/src/caips/caip-10.ts b/packages/caip/src/caips/caip-10.ts new file mode 100644 index 0000000..52d2621 --- /dev/null +++ b/packages/caip/src/caips/caip-10.ts @@ -0,0 +1,46 @@ +import { caip2ChainIdPattern } from "./caip-2" +import type { Caip2ChainId, Caip2ChainIdParts } from "./caip-2" + +/** + * CAIP-10 Spec - Account ID Components + * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md} + * + * account_id: chain_id + ":" + account_address + * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See CAIP-2) + * account_address: [-.%a-zA-Z0-9]{1,128} + */ + +export type Caip10AccountId = `${Caip2ChainId}:${string}` +export type Caip10AccountIdParts = Caip2ChainIdParts & { + accountId: string +} + +export const caip10AccountAddressPattern = "[-.%a-zA-Z0-9]{1,128}" +export const caip10AccountIdPattern = `${caip2ChainIdPattern}:${caip10AccountAddressPattern}` + +export const caip10AccountAddressRegex = new RegExp( + `^${caip10AccountAddressPattern}$` +) +export const caip10AccountIdRegex = new RegExp(`^${caip10AccountIdPattern}$`) + +/** + * Create a CAIP-10 Account ID + * + * @param address - The address to create the CAIP-10 Account ID for + * @param chainId - The CAIP-2 chain ID (e.g. `eip155:1`, `solana`) for this address + * @returns The CAIP-10 Account ID + */ +export function createCaip10AccountId( + chainId: Caip2ChainId, + address: string +): Caip10AccountId { + return `${chainId}:${address}` +} + +export function caip10Parts(caip: Caip10AccountId): Caip10AccountIdParts { + const [namespace, reference, accountId] = caip.split(":") + if (!namespace || !reference || !accountId) { + throw new Error("Invalid CAIP-10 account ID") + } + return { namespace, reference, accountId } +} diff --git a/packages/caip/src/caips/caip-19.ts b/packages/caip/src/caips/caip-19.ts new file mode 100644 index 0000000..86932eb --- /dev/null +++ b/packages/caip/src/caips/caip-19.ts @@ -0,0 +1,37 @@ +import { caip2ChainIdPattern } from "./caip-2" +import type { Caip2ChainId } from "./caip-2" + +/** + * CAIP-19 Spec - Asset Identification Components + * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md} + * + * asset_name: asset_namespace + ":" + asset_reference + * asset_type: chain_id + "/" + asset_name + * asset_id: asset_type + "/" + token_id + * + * asset_namespace: [-a-z0-9]{3,8} + * asset_reference: [-.%a-zA-Z0-9]{1,128} + * token_id: [-.%a-zA-Z0-9]{1,78} + */ + +export type Caip19AssetId = `${Caip2ChainId}:${string}` +export type Caip19AssetName = `${Caip2ChainId}:${string}` +export type Caip19AssetType = `${Caip2ChainId}:${string}` + +export const caip19AssetNamespacePattern = "[-a-z0-9]{3,8}" +export const caip19AssetReferencePattern = "[-.%a-zA-Z0-9]{1,128}" +export const caip19AssetNamePattern = `${caip19AssetNamespacePattern}:${caip19AssetReferencePattern}` +export const caip19AssetTypePattern = `${caip2ChainIdPattern}/${caip19AssetNamePattern}` +export const caip19TokenIdPattern = "[-.%a-zA-Z0-9]{1,78}" +export const caip19AssetIdPattern = `${caip19AssetTypePattern}/${caip19TokenIdPattern}` + +export const caip19AssetNamespaceRegex = new RegExp( + `^${caip19AssetNamespacePattern}$` +) +export const caip19AssetReferenceRegex = new RegExp( + `^${caip19AssetReferencePattern}$` +) +export const caip19AssetNameRegex = new RegExp(`^${caip19AssetNamePattern}$`) +export const caip19AssetTypeRegex = new RegExp(`^${caip19AssetTypePattern}$`) +export const caip19TokenIdRegex = new RegExp(`^${caip19TokenIdPattern}$`) +export const caip19AssetIdRegex = new RegExp(`^${caip19AssetIdPattern}$`) diff --git a/packages/caip/src/caips/caip-2.ts b/packages/caip/src/caips/caip-2.ts new file mode 100644 index 0000000..db4e57c --- /dev/null +++ b/packages/caip/src/caips/caip-2.ts @@ -0,0 +1,49 @@ +/* eslint-disable @cspell/spellchecker */ +/** + * CAIP-2 Spec - Chain ID Components + * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md} + * + * chain_id: namespace + ":" + reference + * namespace: [a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + */ + +export type Caip2ChainId = `${string}:${string}` +export type Caip2ChainIdParts = { + namespace: string + reference: string +} + +export const caip2NamespacePattern = "[a-z0-9]{3,8}" +export const caip2ReferencePattern = "[-_a-zA-Z0-9]{1,32}" +export const caip2ChainIdPattern = `${caip2NamespacePattern}:${caip2ReferencePattern}` + +export const caip2NamespaceRegex = new RegExp(`^${caip2NamespacePattern}$`) +export const caip2ReferenceRegex = new RegExp(`^${caip2ReferencePattern}$`) +export const caip2ChainIdRegex = new RegExp(`^${caip2ChainIdPattern}$`) + +/** + * A set of CAIP-2 chain IDs for select networks + * + * @see {@link https://chainagnostic.org/CAIPs/caip-2} + */ +export const caip2ChainIds = { + ethereumMainnet: "eip155:1", + ethereumSepolia: "eip155:11155111", + baseMainnet: "eip155:8453", + baseSepolia: "eip155:84532", + arbitrumMainnet: "eip155:42161", + arbitrumSepolia: "eip155:421614", + solanaMainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + solanaDevnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" +} as const + +export function caip2Parts(caip: Caip2ChainId): Caip2ChainIdParts { + const [namespace, reference] = caip.split(":") + + if (!namespace || !reference) { + throw new Error("Invalid CAIP-2 chain ID") + } + + return { namespace, reference } +} diff --git a/packages/caip/src/caips/index.ts b/packages/caip/src/caips/index.ts new file mode 100644 index 0000000..f44dbb1 --- /dev/null +++ b/packages/caip/src/caips/index.ts @@ -0,0 +1,3 @@ +export * from "./caip-2" +export * from "./caip-10" +export * from "./caip-19" diff --git a/packages/caip/src/index.ts b/packages/caip/src/index.ts new file mode 100644 index 0000000..5c011de --- /dev/null +++ b/packages/caip/src/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @cspell/spellchecker +export * from "./caips" diff --git a/packages/did/src/caip/schemas.test.ts b/packages/caip/src/schemas/schemas.test.ts similarity index 94% rename from packages/did/src/caip/schemas.test.ts rename to packages/caip/src/schemas/schemas.test.ts index b429085..0728290 100644 --- a/packages/did/src/caip/schemas.test.ts +++ b/packages/caip/src/schemas/schemas.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from "vitest" -import * as valibot from "./schemas/valibot" -import * as zodv3 from "./schemas/zod/v3" -import * as zodv4 from "./schemas/zod/v4" +import * as valibot from "./valibot" +import * as zodv3 from "./zod/v3" +import * as zodv4 from "./zod/v4" import type { Caip10AccountId, Caip19AssetId, Caip19AssetName, Caip19AssetType, Caip2ChainId -} from "./schemas/valibot" +} from "../caips" const schemas = { valibot, @@ -29,6 +29,7 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { for (const chainId of validChainIds) { expect(chainId).toMatchSchema(schemas.caip2ChainIdSchema) + expect(schemas.isCaip2ChainId(chainId)).toBe(true) } }) @@ -46,6 +47,7 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { for (const chainId of invalidChainIds) { expect(chainId).not.toMatchSchema(schemas.caip2ChainIdSchema) + expect(schemas.isCaip2ChainId(chainId)).toBe(false) } }) }) @@ -175,7 +177,7 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { }) it("has correct type inference for CAIP-19 Asset Name", () => { - const assetName: Caip19AssetName = "eip155:erc20" + const assetName: Caip19AssetName = "eip155:1/slip44:60" expect(typeof assetName).toBe("string") }) diff --git a/packages/caip/src/schemas/valibot.ts b/packages/caip/src/schemas/valibot.ts new file mode 100644 index 0000000..a578f7f --- /dev/null +++ b/packages/caip/src/schemas/valibot.ts @@ -0,0 +1,71 @@ +import * as v from "valibot" +import { + caip10AccountIdRegex, + caip19AssetIdRegex, + caip19AssetNameRegex, + caip19AssetTypeRegex, + caip2ChainIdRegex +} from "../caips" +import type { + Caip10AccountId, + Caip19AssetId, + Caip19AssetName, + Caip19AssetType, + Caip2ChainId +} from "../caips" + +export const caip2ChainIdSchema = v.pipe( + v.string(), + v.regex(caip2ChainIdRegex), + v.custom(() => true) +) + +export function isCaip2ChainId(chainId: unknown): chainId is Caip2ChainId { + return v.is(caip2ChainIdSchema, chainId) +} + +export const caip10AccountIdSchema = v.pipe( + v.string(), + v.regex(caip10AccountIdRegex), + v.custom(() => true) +) + +export function isCaip10AccountId( + accountId: unknown +): accountId is Caip10AccountId { + return v.is(caip10AccountIdSchema, accountId) +} + +export const caip19AssetNameSchema = v.pipe( + v.string(), + v.regex(caip19AssetNameRegex), + v.custom(() => true) +) + +export function isCaip19AssetName( + assetName: unknown +): assetName is Caip19AssetName { + return v.is(caip19AssetNameSchema, assetName) +} + +export const caip19AssetTypeSchema = v.pipe( + v.string(), + v.regex(caip19AssetTypeRegex), + v.custom(() => true) +) + +export function isCaip19AssetType( + assetType: unknown +): assetType is Caip19AssetType { + return v.is(caip19AssetTypeSchema, assetType) +} + +export const caip19AssetIdSchema = v.pipe( + v.string(), + v.regex(caip19AssetIdRegex), + v.custom(() => true) +) + +export function isCaip19AssetId(assetId: unknown): assetId is Caip19AssetId { + return v.is(caip19AssetIdSchema, assetId) +} diff --git a/packages/caip/src/schemas/zod/v3.ts b/packages/caip/src/schemas/zod/v3.ts new file mode 100644 index 0000000..f8622bb --- /dev/null +++ b/packages/caip/src/schemas/zod/v3.ts @@ -0,0 +1,66 @@ +import { z } from "zod/v3" +import { + caip10AccountIdRegex, + caip19AssetIdRegex, + caip19AssetNameRegex, + caip19AssetTypeRegex, + caip2ChainIdRegex +} from "../../caips" +import type { + Caip10AccountId, + Caip19AssetId, + Caip19AssetName, + Caip19AssetType, + Caip2ChainId +} from "../../caips" + +export const caip2ChainIdSchema = z + .string() + .regex(caip2ChainIdRegex) + .pipe(z.custom(() => true)) + +export function isCaip2ChainId(chainId: unknown): chainId is Caip2ChainId { + return caip2ChainIdSchema.safeParse(chainId).success +} + +export const caip10AccountIdSchema = z + .string() + .regex(caip10AccountIdRegex) + .pipe(z.custom(() => true)) + +export function isCaip10AccountId( + accountId: unknown +): accountId is Caip10AccountId { + return caip10AccountIdSchema.safeParse(accountId).success +} + +export const caip19AssetNameSchema = z + .string() + .regex(caip19AssetNameRegex) + .pipe(z.custom(() => true)) + +export function isCaip19AssetName( + assetName: unknown +): assetName is Caip19AssetName { + return caip19AssetNameSchema.safeParse(assetName).success +} + +export const caip19AssetTypeSchema = z + .string() + .regex(caip19AssetTypeRegex) + .pipe(z.custom(() => true)) + +export function isCaip19AssetType( + assetType: unknown +): assetType is Caip19AssetType { + return caip19AssetTypeSchema.safeParse(assetType).success +} + +export const caip19AssetIdSchema = z + .string() + .regex(caip19AssetIdRegex) + .pipe(z.custom(() => true)) + +export function isCaip19AssetId(assetId: unknown): assetId is Caip19AssetId { + return caip19AssetIdSchema.safeParse(assetId).success +} diff --git a/packages/caip/src/schemas/zod/v4.ts b/packages/caip/src/schemas/zod/v4.ts new file mode 100644 index 0000000..7d7352f --- /dev/null +++ b/packages/caip/src/schemas/zod/v4.ts @@ -0,0 +1,66 @@ +import * as z from "zod/v4" +import { + caip10AccountIdRegex, + caip19AssetIdRegex, + caip19AssetNameRegex, + caip19AssetTypeRegex, + caip2ChainIdRegex +} from "../../caips" +import type { + Caip10AccountId, + Caip19AssetId, + Caip19AssetName, + Caip19AssetType, + Caip2ChainId +} from "../../caips" + +export const caip2ChainIdSchema = z + .string() + .regex(caip2ChainIdRegex) + .pipe(z.custom(() => true)) + +export function isCaip2ChainId(chainId: unknown): chainId is Caip2ChainId { + return caip2ChainIdSchema.safeParse(chainId).success +} + +export const caip10AccountIdSchema = z + .string() + .regex(caip10AccountIdRegex) + .pipe(z.custom(() => true)) + +export function isCaip10AccountId( + accountId: unknown +): accountId is Caip10AccountId { + return caip10AccountIdSchema.safeParse(accountId).success +} + +export const caip19AssetNameSchema = z + .string() + .regex(caip19AssetNameRegex) + .pipe(z.custom(() => true)) + +export function isCaip19AssetName( + assetName: unknown +): assetName is Caip19AssetName { + return caip19AssetNameSchema.safeParse(assetName).success +} + +export const caip19AssetTypeSchema = z + .string() + .regex(caip19AssetTypeRegex) + .pipe(z.custom(() => true)) + +export function isCaip19AssetType( + assetType: unknown +): assetType is Caip19AssetType { + return caip19AssetTypeSchema.safeParse(assetType).success +} + +export const caip19AssetIdSchema = z + .string() + .regex(caip19AssetIdRegex) + .pipe(z.custom(() => true)) + +export function isCaip19AssetId(assetId: unknown): assetId is Caip19AssetId { + return caip19AssetIdSchema.safeParse(assetId).success +} diff --git a/packages/caip/tsconfig.json b/packages/caip/tsconfig.json new file mode 100644 index 0000000..f9fe93d --- /dev/null +++ b/packages/caip/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@repo/typescript-config/typescript-library.json", + "include": ["."], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/caip/tsdown.config.ts b/packages/caip/tsdown.config.ts new file mode 100644 index 0000000..06262db --- /dev/null +++ b/packages/caip/tsdown.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsdown/config" + +export default defineConfig({ + entry: [ + "src/index.ts", + "src/schemas/zod/v3.ts", + "src/schemas/zod/v4.ts", + "src/schemas/valibot.ts" + ], + dts: true, + silent: true +}) diff --git a/packages/caip/vitest.config.ts b/packages/caip/vitest.config.ts new file mode 100644 index 0000000..122104c --- /dev/null +++ b/packages/caip/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + setupFiles: ["./vitest.setup.ts"], + passWithNoTests: true, + watch: false + } +}) diff --git a/packages/caip/vitest.setup.ts b/packages/caip/vitest.setup.ts new file mode 100644 index 0000000..27f3dec --- /dev/null +++ b/packages/caip/vitest.setup.ts @@ -0,0 +1,13 @@ +/** + * Adds `toMatchSchema` matchers + * + * @example + * ```ts + * expect(value).toMatchSchema(schema) + * expect(value).not.toMatchSchema(schema) + * expect(value).toMatchSchema(schema, (parsed) => { + * // ... additional assertions + * }) + * ``` + */ +import "standard-parse/test-matchers/vitest" diff --git a/packages/did/package.json b/packages/did/package.json index cef659d..d7b0f3c 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -55,6 +55,7 @@ "test": "vitest" }, "dependencies": { + "@agentcommercekit/caip": "workspace:*", "@agentcommercekit/keys": "workspace:*", "did-resolver": "^4.1.0", "key-did-resolver": "^4.0.0", diff --git a/packages/did/src/caip/caip.test.ts b/packages/did/src/caip/caip.test.ts deleted file mode 100644 index cdfaaa8..0000000 --- a/packages/did/src/caip/caip.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, it } from "vitest" -import { createCaip10AccountId, isCaip2ChainId } from "./caip" - -describe("isCaip2ChainId", () => { - it("returns true for valid CAIP-2 chain IDs", () => { - expect(isCaip2ChainId("eip155:1")).toBe(true) - expect(isCaip2ChainId("eip155:11155111")).toBe(true) - expect(isCaip2ChainId("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")).toBe(true) - expect(isCaip2ChainId("bitcoin:mainnet")).toBe(true) - expect(isCaip2ChainId("cosmos:cosmoshub-4")).toBe(true) - }) - - it("returns false for invalid chain IDs", () => { - expect(isCaip2ChainId("invalid")).toBe(false) - expect(isCaip2ChainId("eip155")).toBe(false) - expect(isCaip2ChainId(":1")).toBe(false) - expect(isCaip2ChainId("eip155:")).toBe(false) - expect(isCaip2ChainId("")).toBe(false) - expect(isCaip2ChainId(null)).toBe(false) - expect(isCaip2ChainId(undefined)).toBe(false) - expect(isCaip2ChainId(123)).toBe(false) - }) - - it("returns false for chain IDs with invalid namespace length", () => { - expect(isCaip2ChainId("ab:1")).toBe(false) // too short - expect(isCaip2ChainId("verylongnamespace:1")).toBe(false) // too long - }) - - it("returns false for chain IDs with invalid characters", () => { - expect(isCaip2ChainId("EIP155:1")).toBe(false) // uppercase not allowed in namespace - expect(isCaip2ChainId("eip-155:1")).toBe(false) // hyphen not allowed in namespace - }) -}) - -describe("createCaip10AccountId", () => { - it("creates a caip 10 account ID for EVM address", () => { - const result = createCaip10AccountId( - "eip155:1", - "0x1234567890123456789012345678901234567890" - ) - expect(result).toBe("eip155:1:0x1234567890123456789012345678901234567890") - }) - - it("creates a caip 10 account ID for Solana address", () => { - const result = createCaip10AccountId( - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" - ) - expect(result).toBe( - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" - ) - }) -}) diff --git a/packages/did/src/caip/caip.ts b/packages/did/src/caip/caip.ts deleted file mode 100644 index b72d4e3..0000000 --- a/packages/did/src/caip/caip.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as v from "valibot" -import { caip10AccountIdSchema, caip2ChainIdSchema } from "./schemas/valibot" -import type { Caip10AccountId, Caip2ChainId } from "./types" - -/** - * A set of CAIP-2 chain IDs for select networks - * - * @see {@link https://chainagnostic.org/CAIPs/caip-2} - */ -export const caip2ChainIds = { - ethereumMainnet: "eip155:1", - ethereumSepolia: "eip155:11155111", - baseMainnet: "eip155:8453", - baseSepolia: "eip155:84532", - arbitrumMainnet: "eip155:42161", - arbitrumSepolia: "eip155:421614", - solanaMainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - solanaDevnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" -} as const - -/** - * Checks if a given string is a valid CAIP-2 chain ID (`namespace:reference`) - * chain_id: namespace + ":" + reference - * namespace: [-a-z0-9]{3,8} - * reference: [-_a-zA-Z0-9]{1,32} - * - * @param chainId - The chain ID to check - * @returns `true` if the chain ID is a valid CAIP-2 chain ID, `false` otherwise - */ -export function isCaip2ChainId(chainId: unknown): chainId is Caip2ChainId { - return v.is(caip2ChainIdSchema, chainId) -} - -/** - * Checks if a given string is a valid CAIP-10 account ID - */ -export function isCaip10AccountId( - accountId: unknown -): accountId is Caip10AccountId { - return v.is(caip10AccountIdSchema, accountId) -} - -/** - * Create a CAIP-10 Account ID - * - * @param address - The address to create the CAIP-10 Account ID for - * @param chainId - The CAIP-2 chain ID (e.g. `eip155:1`, `solana`) for this address - * @returns The CAIP-10 Account ID - */ -export function createCaip10AccountId( - chainId: Caip2ChainId, - address: string -): Caip10AccountId { - return `${chainId}:${address}` -} - -interface Caip2Parts { - namespace: string - reference: string -} - -interface Caip10Parts extends Caip2Parts { - accountId: string -} - -export function caip2Parts(caip: Caip2ChainId): Caip2Parts { - const [namespace, reference] = caip.split(":") - if (!namespace || !reference) { - throw new Error("Invalid CAIP-2 chain ID") - } - return { namespace, reference } -} - -export function caip10Parts(caip: Caip10AccountId): Caip10Parts { - const [namespace, reference, accountId] = caip.split(":") - if (!namespace || !reference || !accountId) { - throw new Error("Invalid CAIP-10 account ID") - } - return { namespace, reference, accountId } -} diff --git a/packages/did/src/caip/index.ts b/packages/did/src/caip/index.ts deleted file mode 100644 index 399fc3b..0000000 --- a/packages/did/src/caip/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./caip" -export type * from "./types" diff --git a/packages/did/src/caip/schemas/core.ts b/packages/did/src/caip/schemas/core.ts deleted file mode 100644 index 756ef78..0000000 --- a/packages/did/src/caip/schemas/core.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * CAIP Schema Patterns - * - * Core regex patterns for CAIP specifications that can be composed - * to build more complex CAIP schemas across validation libraries. - * - * @see {@link https://github.com/ChainAgnostic/CAIPs} - */ - -/** - * CAIP-2 Spec - Chain ID Components - * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md} - * - * chain_id: namespace + ":" + reference - * namespace: [a-z0-9]{3,8} - * reference: [-_a-zA-Z0-9]{1,32} - */ - -/** - * CAIP-2 namespace pattern: [a-z0-9]{3,8} - * @example "eip155", "solana", "cosmos" - */ -export const CAIP2_NAMESPACE_PATTERN = "[a-z0-9]{3,8}" - -/** - * CAIP-2 reference pattern: [-_a-zA-Z0-9]{1,32} - * @example "1", "11155111", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - */ -export const CAIP2_REFERENCE_PATTERN = "[-_a-zA-Z0-9]{1,32}" - -/** - * CAIP-2 chain_id pattern: namespace + ":" + reference - * @example "eip155:1", "eip155:11155111", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - */ -export const CAIP2_CHAIN_ID_PATTERN = `${CAIP2_NAMESPACE_PATTERN}:${CAIP2_REFERENCE_PATTERN}` - -/** - * CAIP-10 Spec - Account ID Components - * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md} - * - * account_id: chain_id + ":" + account_address - * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See CAIP-2) - * account_address: [-.%a-zA-Z0-9]{1,128} - */ - -/** - * CAIP-10 account_address pattern: [-.%a-zA-Z0-9]{1,128} - * @example "0x1234567890123456789012345678901234567890", "FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" - */ -export const CAIP10_ACCOUNT_ADDRESS_PATTERN = "[-.%a-zA-Z0-9]{1,128}" - -/** - * CAIP-10 account_id pattern: chain_id + ":" + account_address - * @example "eip155:1:0x1234567890123456789012345678901234567890", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:FNoGHiv7DKPLXHfuhiEWpJ8qYitawGkuaYwfYkuvFk1P" - */ -export const CAIP10_ACCOUNT_ID_PATTERN = `${CAIP2_CHAIN_ID_PATTERN}:${CAIP10_ACCOUNT_ADDRESS_PATTERN}` - -/** - * CAIP-19 Spec - Asset Identification Components - * @see {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md} - * - * asset_name: asset_namespace + ":" + asset_reference - * asset_type: chain_id + "/" + asset_name - * asset_id: asset_type + "/" + token_id - * - * asset_namespace: [-a-z0-9]{3,8} - * asset_reference: [-.%a-zA-Z0-9]{1,128} - * token_id: [-.%a-zA-Z0-9]{1,78} - */ - -/** - * CAIP-19 asset_namespace pattern: [-a-z0-9]{3,8} - * @example "erc20", "erc721", "spl" - */ -export const CAIP19_ASSET_NAMESPACE_PATTERN = "[-a-z0-9]{3,8}" - -/** - * CAIP-19 asset_reference pattern: [-.%a-zA-Z0-9]{1,128} - * @example "0xA0b86a33E6441e6e80A7C1A00000000001", "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - */ -export const CAIP19_ASSET_REFERENCE_PATTERN = "[-.%a-zA-Z0-9]{1,128}" - -/** - * CAIP-19 asset_name pattern: asset_namespace + ":" + asset_reference - * @example "erc20:0xA0b86a33E6441e6e80A7C1A00000000001", "spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - */ -export const CAIP19_ASSET_NAME_PATTERN = `${CAIP19_ASSET_NAMESPACE_PATTERN}:${CAIP19_ASSET_REFERENCE_PATTERN}` - -/** - * CAIP-19 asset_type pattern: chain_id + "/" + asset_name - * @example "eip155:1/erc20:0xA0b86a33E6441e6e80A7C1A00000000001", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - */ -export const CAIP19_ASSET_TYPE_PATTERN = `${CAIP2_CHAIN_ID_PATTERN}/${CAIP19_ASSET_NAME_PATTERN}` - -/** - * CAIP-19 token_id pattern: [-.%a-zA-Z0-9]{1,78} - * @example "1", "42", "CryptoPunk.3100" - */ -export const CAIP19_TOKEN_ID_PATTERN = "[-.%a-zA-Z0-9]{1,78}" - -/** - * CAIP-19 asset_id pattern: asset_type + "/" + token_id - * @example "eip155:1/erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1", "eip155:1/erc721:0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/42" - */ -export const CAIP19_ASSET_ID_PATTERN = `${CAIP19_ASSET_TYPE_PATTERN}/${CAIP19_TOKEN_ID_PATTERN}` diff --git a/packages/did/src/caip/schemas/valibot.ts b/packages/did/src/caip/schemas/valibot.ts deleted file mode 100644 index 2236716..0000000 --- a/packages/did/src/caip/schemas/valibot.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as v from "valibot" -import { - CAIP10_ACCOUNT_ID_PATTERN, - CAIP19_ASSET_ID_PATTERN, - CAIP19_ASSET_NAME_PATTERN, - CAIP19_ASSET_TYPE_PATTERN, - CAIP2_CHAIN_ID_PATTERN -} from "./core" - -export const caip2ChainIdSchema = v.pipe( - v.string(), - v.regex(new RegExp(`^${CAIP2_CHAIN_ID_PATTERN}$`)), - v.custom<`${string}:${string}`>(() => true) -) - -export type Caip2ChainId = v.InferOutput - -export const caip10AccountIdSchema = v.pipe( - v.string(), - v.regex(new RegExp(`^${CAIP10_ACCOUNT_ID_PATTERN}$`)), - v.custom<`${Caip2ChainId}:${string}`>(() => true) -) - -export type Caip10AccountId = v.InferOutput - -export const caip19AssetNameSchema = v.pipe( - v.string(), - v.regex(new RegExp(`^${CAIP19_ASSET_NAME_PATTERN}$`)), - v.custom<`${string}:${string}`>(() => true) -) - -export type Caip19AssetName = v.InferOutput - -export const caip19AssetTypeSchema = v.pipe( - v.string(), - v.regex(new RegExp(`^${CAIP19_ASSET_TYPE_PATTERN}$`)), - v.custom<`${Caip2ChainId}/${Caip19AssetName}`>(() => true) -) - -export type Caip19AssetType = v.InferOutput - -export const caip19AssetIdSchema = v.pipe( - v.string(), - v.regex(new RegExp(`^${CAIP19_ASSET_ID_PATTERN}$`)), - v.custom<`${Caip2ChainId}/${Caip19AssetName}/${string}`>(() => true) -) - -export type Caip19AssetId = v.InferOutput diff --git a/packages/did/src/caip/schemas/zod/v3.ts b/packages/did/src/caip/schemas/zod/v3.ts deleted file mode 100644 index 57cb6cd..0000000 --- a/packages/did/src/caip/schemas/zod/v3.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod/v3" -import { - CAIP10_ACCOUNT_ID_PATTERN, - CAIP19_ASSET_ID_PATTERN, - CAIP19_ASSET_NAME_PATTERN, - CAIP19_ASSET_TYPE_PATTERN, - CAIP2_CHAIN_ID_PATTERN -} from "../core" - -export const caip2ChainIdSchema = z - .string() - .regex(new RegExp(`^${CAIP2_CHAIN_ID_PATTERN}$`)) - -export const caip10AccountIdSchema = z - .string() - .regex(new RegExp(`^${CAIP10_ACCOUNT_ID_PATTERN}$`)) - -export const caip19AssetNameSchema = z - .string() - .regex(new RegExp(`^${CAIP19_ASSET_NAME_PATTERN}$`)) - -export const caip19AssetTypeSchema = z - .string() - .regex(new RegExp(`^${CAIP19_ASSET_TYPE_PATTERN}$`)) - -export const caip19AssetIdSchema = z - .string() - .regex(new RegExp(`^${CAIP19_ASSET_ID_PATTERN}$`)) diff --git a/packages/did/src/caip/schemas/zod/v4.ts b/packages/did/src/caip/schemas/zod/v4.ts deleted file mode 100644 index 15382ad..0000000 --- a/packages/did/src/caip/schemas/zod/v4.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as z from "zod/v4" -import { - CAIP10_ACCOUNT_ID_PATTERN, - CAIP19_ASSET_ID_PATTERN, - CAIP19_ASSET_NAME_PATTERN, - CAIP19_ASSET_TYPE_PATTERN, - CAIP2_CHAIN_ID_PATTERN -} from "../core" - -export const caip2ChainIdSchema = z - .string() - .regex(new RegExp(`^${CAIP2_CHAIN_ID_PATTERN}$`)) - -export const caip10AccountIdSchema = z - .string() - .regex(new RegExp(`^${CAIP10_ACCOUNT_ID_PATTERN}$`)) - -export const caip19AssetNameSchema = z - .string() - .regex(new RegExp(`^${CAIP19_ASSET_NAME_PATTERN}$`)) - -export const caip19AssetTypeSchema = z - .string() - .regex(new RegExp(`^${CAIP19_ASSET_TYPE_PATTERN}$`)) - -export const caip19AssetIdSchema = z - .string() - .regex(new RegExp(`^${CAIP19_ASSET_ID_PATTERN}$`)) diff --git a/packages/did/src/caip/types.ts b/packages/did/src/caip/types.ts deleted file mode 100644 index 2857d2b..0000000 --- a/packages/did/src/caip/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type { - Caip2ChainId, - Caip10AccountId, - Caip19AssetId, - Caip19AssetName, - Caip19AssetType -} from "./schemas/valibot" diff --git a/packages/did/src/index.ts b/packages/did/src/index.ts index 8fc8418..0366c95 100644 --- a/packages/did/src/index.ts +++ b/packages/did/src/index.ts @@ -1,4 +1,3 @@ -export * from "./caip" export * from "./did-document" export * from "./did-resolvers/did-resolver" export * from "./did-resolvers/get-did-resolver" diff --git a/packages/did/src/methods/did-key.test.ts b/packages/did/src/methods/did-key.test.ts index c279c23..ba983f3 100644 --- a/packages/did/src/methods/did-key.test.ts +++ b/packages/did/src/methods/did-key.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @cspell/spellchecker */ import { generateKeypair, jwkToKeypair } from "@agentcommercekit/keys" import { describe, expect, it } from "vitest" import { createDidKeyUri, isDidKeyUri } from "./did-key" diff --git a/packages/did/src/methods/did-pkh.ts b/packages/did/src/methods/did-pkh.ts index 3bb455c..99a6185 100644 --- a/packages/did/src/methods/did-pkh.ts +++ b/packages/did/src/methods/did-pkh.ts @@ -1,19 +1,18 @@ /* eslint-disable @cspell/spellchecker */ +import { caip10Parts, createCaip10AccountId } from "@agentcommercekit/caip" +import { + isCaip10AccountId, + isCaip2ChainId +} from "@agentcommercekit/caip/schemas/valibot" import { base58ToBytes, isBase58, publicKeyBytesToJwk } from "@agentcommercekit/keys/encoding" -import { - caip10Parts, - createCaip10AccountId, - isCaip10AccountId, - isCaip2ChainId -} from "../caip/caip" import { createDidDocument } from "../create-did-document" import type { DidUri } from "../did-uri" -import type { Caip10AccountId, Caip2ChainId } from "../schemas/valibot" import type { DidUriWithDocument } from "../types" +import type { Caip10AccountId, Caip2ChainId } from "@agentcommercekit/caip" import type { VerificationMethod } from "did-resolver" /** diff --git a/packages/did/src/schemas/valibot.ts b/packages/did/src/schemas/valibot.ts index d284a7d..d51e148 100644 --- a/packages/did/src/schemas/valibot.ts +++ b/packages/did/src/schemas/valibot.ts @@ -1,10 +1,8 @@ +import { caip2ChainIdSchema } from "@agentcommercekit/caip/schemas/valibot" import * as v from "valibot" -import { caip2ChainIdSchema } from "../caip/schemas/valibot" import { isDidUri } from "../did-uri" import type { DidUri } from "../did-uri" -export * from "../caip/schemas/valibot" - export const didUriSchema = v.custom(isDidUri, "Invalid DID format") /** diff --git a/packages/did/src/schemas/zod/v3.ts b/packages/did/src/schemas/zod/v3.ts index c305826..54da134 100644 --- a/packages/did/src/schemas/zod/v3.ts +++ b/packages/did/src/schemas/zod/v3.ts @@ -1,10 +1,8 @@ +import { caip2ChainIdSchema } from "@agentcommercekit/caip/schemas/zod/v3" import { z } from "zod/v3" -import { caip2ChainIdSchema } from "../../caip/schemas/zod/v3" import { isDidUri } from "../../did-uri" import type { DidUri } from "../../did-uri" -export * from "../../caip/schemas/zod/v3" - export const didUriSchema = z.custom(isDidUri, "Invalid DID format") /** diff --git a/packages/did/src/schemas/zod/v4.ts b/packages/did/src/schemas/zod/v4.ts index 6f5a584..aae8153 100644 --- a/packages/did/src/schemas/zod/v4.ts +++ b/packages/did/src/schemas/zod/v4.ts @@ -1,10 +1,8 @@ +import { caip2ChainIdSchema } from "@agentcommercekit/caip/schemas/zod/v4" import * as z from "zod/v4" -import { caip2ChainIdSchema } from "../../caip/schemas/zod/v4" import { isDidUri } from "../../did-uri" import type { DidUri } from "../../did-uri" -export * from "../../caip/schemas/zod/v4" - export const didUriSchema = z.custom(isDidUri, "Invalid DID format") /** diff --git a/packages/jwt/src/jwt-string.test.ts b/packages/jwt/src/jwt-string.test.ts index dc69e1d..a9c8f1c 100644 --- a/packages/jwt/src/jwt-string.test.ts +++ b/packages/jwt/src/jwt-string.test.ts @@ -5,7 +5,6 @@ describe("isJwtString", () => { it("should return true for a valid JWT string", () => { expect( isJwtString( - // eslint-disable-next-line @cspell/spellchecker "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" ) ).toBe(true) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c75c2c..3d69701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -507,6 +507,9 @@ importers: '@agentcommercekit/ack-pay': specifier: workspace:* version: link:../ack-pay + '@agentcommercekit/caip': + specifier: workspace:* + version: link:../caip '@agentcommercekit/did': specifier: workspace:* version: link:../did @@ -548,8 +551,41 @@ importers: specifier: ^3.25.0 version: 3.25.4 + packages/caip: + devDependencies: + '@repo/eslint-config': + specifier: workspace:* + version: link:../../tools/eslint-config + '@repo/typescript-config': + specifier: workspace:* + version: link:../../tools/typescript-config + eslint: + specifier: ^9.27.0 + version: 9.27.0(jiti@2.4.2) + standard-parse: + specifier: ^0.3.0 + version: 0.3.0(valibot@1.1.0(typescript@5.8.3))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0))(zod@3.25.4) + tsdown: + specifier: ^0.11.12 + version: 0.11.12(typescript@5.8.3) + typescript: + specifier: ^5 + version: 5.8.3 + valibot: + specifier: ^1.1.0 + version: 1.1.0(typescript@5.8.3) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.19)(jiti@2.4.2)(lightningcss@1.29.3)(tsx@4.20.3)(yaml@2.8.0) + zod: + specifier: ^3.25.0 + version: 3.25.4 + packages/did: dependencies: + '@agentcommercekit/caip': + specifier: workspace:* + version: link:../caip '@agentcommercekit/keys': specifier: workspace:* version: link:../keys diff --git a/tools/eslint-config/base.js b/tools/eslint-config/base.js index a23e9a2..49a485f 100644 --- a/tools/eslint-config/base.js +++ b/tools/eslint-config/base.js @@ -151,6 +151,7 @@ export function config({ root }) { { files: ["**/*.test.*"], rules: { + "@cspell/spellchecker": "off", "@typescript-eslint/no-non-null-assertion": "off" } }, From 4c42599bb78d0d2b1f4fb0c343e1b2b6bce863b3 Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Fri, 25 Jul 2025 23:41:23 -0400 Subject: [PATCH 5/6] Fix CAIP19 asset test cases --- packages/caip/src/caips/caip-19.ts | 6 ++--- packages/caip/src/schemas/schemas.test.ts | 27 ++++++++++++----------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/caip/src/caips/caip-19.ts b/packages/caip/src/caips/caip-19.ts index 86932eb..c972f29 100644 --- a/packages/caip/src/caips/caip-19.ts +++ b/packages/caip/src/caips/caip-19.ts @@ -14,9 +14,9 @@ import type { Caip2ChainId } from "./caip-2" * token_id: [-.%a-zA-Z0-9]{1,78} */ -export type Caip19AssetId = `${Caip2ChainId}:${string}` -export type Caip19AssetName = `${Caip2ChainId}:${string}` -export type Caip19AssetType = `${Caip2ChainId}:${string}` +export type Caip19AssetName = `${string}:${string}` // asset_namespace:asset_reference +export type Caip19AssetType = `${Caip2ChainId}/${Caip19AssetName}` // chain_id/asset_name +export type Caip19AssetId = `${Caip19AssetType}/${string}` // asset_type/token_id export const caip19AssetNamespacePattern = "[-a-z0-9]{3,8}" export const caip19AssetReferencePattern = "[-.%a-zA-Z0-9]{1,128}" diff --git a/packages/caip/src/schemas/schemas.test.ts b/packages/caip/src/schemas/schemas.test.ts index 0728290..136bdd2 100644 --- a/packages/caip/src/schemas/schemas.test.ts +++ b/packages/caip/src/schemas/schemas.test.ts @@ -82,10 +82,10 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { describe("CAIP-19 Asset Name Schema", () => { it("validates correct CAIP-19 asset names", () => { const validAssetNames = [ - "eip155:erc20", - "eip155:erc721", - "solana:spl", - "bitcoin:btc" + "slip44:60", + "erc20:0xa0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c", + "erc721:0xb0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c", + "spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] for (const assetName of validAssetNames) { @@ -111,9 +111,9 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { describe("CAIP-19 Asset Type Schema", () => { it("validates correct CAIP-19 asset types", () => { const validAssetTypes = [ - "eip155:1/eip155:erc20", - "eip155:11155111/eip155:erc721", - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/solana:spl" + "eip155:1/erc20:0xa0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c", + "eip155:11155111/erc721:0xb0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] for (const assetType of validAssetTypes) { @@ -139,9 +139,9 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { describe("CAIP-19 Asset ID Schema", () => { it("validates correct CAIP-19 asset IDs", () => { const validAssetIds = [ - "eip155:1/eip155:erc20/0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", - "eip155:11155111/eip155:erc721/123", - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/solana:spl/5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + "eip155:1/erc20:0xa0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c/0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", + "eip155:11155111/erc721:0xb0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c/123", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" ] for (const assetId of validAssetIds) { @@ -177,18 +177,19 @@ describe.each(Object.entries(schemas))("CAIP (%s)", (_name, schemas) => { }) it("has correct type inference for CAIP-19 Asset Name", () => { - const assetName: Caip19AssetName = "eip155:1/slip44:60" + const assetName: Caip19AssetName = "slip44:60" expect(typeof assetName).toBe("string") }) it("has correct type inference for CAIP-19 Asset Type", () => { - const assetType: Caip19AssetType = "eip155:1/eip155:erc20" + const assetType: Caip19AssetType = + "eip155:1/erc20:0xa0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c" expect(typeof assetType).toBe("string") }) it("has correct type inference for CAIP-19 Asset ID", () => { const assetId: Caip19AssetId = - "eip155:1/eip155:erc20/0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6" + "eip155:1/erc20:0xa0b86a33e6441b8c4c8c8c8c8c8c8c8c8c8c8c8c/0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6" expect(typeof assetId).toBe("string") }) }) From 8b70a9b713e44bb8708a24b7c8a5ad20e1639fec Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Fri, 25 Jul 2025 23:48:34 -0400 Subject: [PATCH 6/6] Add createDidPkhDocumentFromDidPkhUri method --- .../did/src/did-resolvers/pkh-did-resolver.ts | 7 +-- packages/did/src/methods/did-pkh.ts | 44 +++++++++++-------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/did/src/did-resolvers/pkh-did-resolver.ts b/packages/did/src/did-resolvers/pkh-did-resolver.ts index 2712b62..24453b4 100644 --- a/packages/did/src/did-resolvers/pkh-did-resolver.ts +++ b/packages/did/src/did-resolvers/pkh-did-resolver.ts @@ -2,8 +2,7 @@ * A `did:pkh` resolver for use with `did-resolver` */ import { - caip10AccountIdFromDidPkhUri, - createDidPkhDocumentFromCaip10AccountId, + createDidPkhDocumentFromDidPkhUri, isDidPkhUri } from "../methods/did-pkh" import type { DIDResolutionResult, DIDResolver } from "did-resolver" @@ -17,9 +16,7 @@ export async function resolve(did: string): Promise { } } - const caip10AccountId = caip10AccountIdFromDidPkhUri(did) - const { didDocument } = - createDidPkhDocumentFromCaip10AccountId(caip10AccountId) + const { didDocument } = createDidPkhDocumentFromDidPkhUri(did) return Promise.resolve({ didDocument, diff --git a/packages/did/src/methods/did-pkh.ts b/packages/did/src/methods/did-pkh.ts index 99a6185..4df1d4e 100644 --- a/packages/did/src/methods/did-pkh.ts +++ b/packages/did/src/methods/did-pkh.ts @@ -52,27 +52,13 @@ export function didPkhParts( throw new Error("Invalid did:pkh URI") } - const [did, method, chainNamespace, chainReference, ...rest] = - didUri.split(":") - - // Build the address from the remaining parts - const address = rest.join(":") - - if ( - did !== "did" || - method !== "pkh" || - !chainNamespace?.length || - !chainReference?.length || - !address.length - ) { - throw new Error("Invalid did:pkh URI") - } - - if (!isCaip2ChainId(`${chainNamespace}:${chainReference}`)) { + const caip10AccountId = didUri.replace("did:pkh:", "") + if (!isCaip10AccountId(caip10AccountId)) { throw new Error("Invalid did:pkh URI") } + const { namespace, reference, accountId } = caip10Parts(caip10AccountId) - return [did, method, chainNamespace, chainReference, address] + return ["did", "pkh", namespace, reference, accountId] } /** @@ -177,6 +163,28 @@ const jsonLdContexts = { ] } +/** + * Create a did:pkh document from a did:pkh URI + * + * @example + * ```ts + * const didDocument = createDidPkhDocumentFromDidPkhUri( + * "did:pkh:eip155:1:0x1234567890123456789012345678901234567890" + * ) + * ``` + * + * @param didUri - The did:pkh URI + * @param controller - The controller of the did:pkh document + * @returns The did:pkh document + */ +export function createDidPkhDocumentFromDidPkhUri( + didUri: DidPkhUri, + controller?: DidUri +): DidUriWithDocument { + const caip10AccountId = caip10AccountIdFromDidPkhUri(didUri) + return createDidPkhDocumentFromCaip10AccountId(caip10AccountId, controller) +} + /** * Create a did:pkh document from a CAIP-10 account ID *