diff --git a/packages/atproto-browser/app/at/page.tsx b/packages/atproto-browser/app/at/page.tsx
index 97f196bd..4d5b0ab4 100644
--- a/packages/atproto-browser/app/at/page.tsx
+++ b/packages/atproto-browser/app/at/page.tsx
@@ -1,6 +1,7 @@
import {
DidResolver,
getHandle,
+ getKey,
getPds,
HandleResolver,
} from "@atproto/identity";
@@ -12,6 +13,7 @@ import { AtBlob } from "./_lib/at-blob";
import { CollectionItems } from "./_lib/collection";
import { SWRConfig } from "swr";
import { listRecords } from "@/lib/atproto";
+import { verifyRecords } from "@atproto/repo";
const didResolver = new DidResolver({});
const resolveDid = cache((did: string) => didResolver.resolve(did));
@@ -122,12 +124,78 @@ export default async function AtPage({
-
Record
+
+ Record
+
+ 🤔
+
+ }
+ >
+
+
+
);
}
+async function RecordVerificationBadge({
+ did,
+ collection,
+ rkey,
+}: {
+ did: string;
+ collection: string;
+ rkey: string;
+}) {
+ const didDoc = (await resolveDid(did))!;
+ const pds = getPds(didDoc);
+ if (!pds) {
+ return ❌;
+ }
+
+ const verifyRecordsUrl = new URL(`${pds}/xrpc/com.atproto.sync.getRecord`);
+ verifyRecordsUrl.searchParams.set("did", did);
+ verifyRecordsUrl.searchParams.set("collection", collection);
+ verifyRecordsUrl.searchParams.set("rkey", rkey);
+
+ const response = await fetch(verifyRecordsUrl, {
+ headers: {
+ accept: "application/vnd.ipld.car",
+ },
+ });
+
+ if (!response.ok) {
+ return (
+
+ ❌
+
+ );
+ }
+ const car = new Uint8Array(await response.arrayBuffer());
+ const key = getKey(didDoc);
+ if (!key) {
+ return ❌;
+ }
+
+ try {
+ await verifyRecords(car, did, key);
+ return 🔒;
+ } catch (e) {
+ if (e instanceof Error) {
+ return ❌;
+ } else {
+ return ❌;
+ }
+ }
+}
+
async function Author({ did }: { did: string }) {
const didDocument = await resolveDid(did);
if (!didDocument) {
diff --git a/packages/atproto-browser/package.json b/packages/atproto-browser/package.json
index 68f94dcc..ad13e943 100644
--- a/packages/atproto-browser/package.json
+++ b/packages/atproto-browser/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@atproto/did": "^0.1.0",
"@atproto/identity": "^0.4.0",
+ "@atproto/repo": "^0.4.1",
"@atproto/syntax": "^0.3.0",
"next": "15.0.0-rc.0",
"react": "19.0.0-rc-f994737d14-20240522",
@@ -26,6 +27,7 @@
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "15.0.0-rc.0",
+ "tsx": "^4.16.5",
"typescript": "^5"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0b25335d..02280c70 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
'@atproto/identity':
specifier: ^0.4.0
version: 0.4.0
+ '@atproto/repo':
+ specifier: ^0.4.1
+ version: 0.4.1
'@atproto/syntax':
specifier: ^0.3.0
version: 0.3.0
@@ -63,6 +66,9 @@ importers:
eslint-config-next:
specifier: 15.0.0-rc.0
version: 15.0.0-rc.0(eslint@8.57.0)(typescript@5.5.2)
+ tsx:
+ specifier: ^4.16.5
+ version: 4.16.5
typescript:
specifier: ^5
version: 5.5.2
@@ -336,6 +342,9 @@ packages:
'@atproto/common@0.4.0':
resolution: {integrity: sha512-yOXuPlCjT/OK9j+neIGYn9wkxx/AlxQSucysAF0xgwu0Ji8jAtKBf9Jv6R5ObYAjAD/kVUvEYumle+Yq/R9/7g==}
+ '@atproto/common@0.4.1':
+ resolution: {integrity: sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==}
+
'@atproto/crypto@0.1.0':
resolution: {integrity: sha512-9xgFEPtsCiJEPt9o3HtJT30IdFTGw5cQRSJVIy5CFhqBA4vDLcdXiRDLCjkzHEVbtNCsHUW6CrlfOgbeLPcmcg==}
@@ -358,6 +367,9 @@ packages:
'@atproto/repo@0.4.0':
resolution: {integrity: sha512-LB0DF/D8r8hB+qiGB0sWZuq7TSJYbWel+t572aCrLeCOmbRgnLkGPLUTOOUvLFYv8xz1BPZTbI8hy/vcUV79VA==}
+ '@atproto/repo@0.4.1':
+ resolution: {integrity: sha512-DXv/cBwRcAM0KFb4SwafcQBONd0g31QUNLfjTri1bg5adCbX3bxxE4fCPpQM9Qc3+5lcCkTL/EniHW1j3UQjVA==}
+
'@atproto/syntax@0.3.0':
resolution: {integrity: sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==}
@@ -5258,6 +5270,11 @@ packages:
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+ tsx@4.16.5:
+ resolution: {integrity: sha512-ArsiAQHEW2iGaqZ8fTA1nX0a+lN5mNTyuGRRO6OW3H/Yno1y9/t1f9YOI1Cfoqz63VAthn++ZYcbDP7jPflc+A==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
@@ -5702,6 +5719,15 @@ snapshots:
multiformats: 9.9.0
pino: 8.21.0
+ '@atproto/common@0.4.1':
+ dependencies:
+ '@atproto/common-web': 0.3.0
+ '@ipld/dag-cbor': 7.0.3
+ cbor-x: 1.5.9
+ iso-datestring-validator: 2.2.2
+ multiformats: 9.9.0
+ pino: 8.21.0
+
'@atproto/crypto@0.1.0':
dependencies:
'@noble/secp256k1': 1.7.1
@@ -5793,6 +5819,18 @@ snapshots:
uint8arrays: 3.0.0
zod: 3.23.8
+ '@atproto/repo@0.4.1':
+ dependencies:
+ '@atproto/common': 0.4.1
+ '@atproto/common-web': 0.3.0
+ '@atproto/crypto': 0.4.0
+ '@atproto/lexicon': 0.4.0
+ '@ipld/car': 3.2.4
+ '@ipld/dag-cbor': 7.0.3
+ multiformats: 9.9.0
+ uint8arrays: 3.0.0
+ zod: 3.23.8
+
'@atproto/syntax@0.3.0': {}
'@atproto/xrpc-server@0.5.1(bufferutil@4.0.8)(utf-8-validate@6.0.3)':
@@ -11594,6 +11632,13 @@ snapshots:
tslib: 1.14.1
typescript: 5.5.2
+ tsx@4.16.5:
+ dependencies:
+ esbuild: 0.21.5
+ get-tsconfig: 4.7.5
+ optionalDependencies:
+ fsevents: 2.3.3
+
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1