@zkmedusa/passport-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @zkmedusa/passport-sdk
2
+
3
+ Use Medusa Passports in your app, presale, or whitelist without rebuilding verification logic.
4
+
5
+ **Full documentation:** [zkmedusa.com/docs](https://www.zkmedusa.com/docs)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @zkmedusa/passport-sdk
11
+ ```
12
+
13
+ ## Quick start — verify locally
14
+
15
+ ```typescript
16
+ import {
17
+ parsePassportJson,
18
+ verifyPassport,
19
+ } from "@zkmedusa/passport-sdk";
20
+
21
+ const passport = parsePassportJson(passportJsonFromUser);
22
+
23
+ const result = verifyPassport(passport, {
24
+ issuerPublicKey: "749d6135f3d883950ff82f82bc2119e8b6e469ebfc4163dd10e1cc079b723565",
25
+ minTier: 2, // require SILVER+
26
+ });
27
+
28
+ if (result.valid) {
29
+ console.log(`Approved: ${result.tierLabel}`);
30
+ } else {
31
+ console.log(result.errors);
32
+ }
33
+ ```
34
+
35
+ Fetch the issuer public key dynamically:
36
+
37
+ ```typescript
38
+ import { MedusaPassportClient } from "@zkmedusa/passport-sdk";
39
+
40
+ const client = new MedusaPassportClient({
41
+ baseUrl: "https://your-medusa-app.com",
42
+ });
43
+
44
+ const result = await client.verify(passport);
45
+ ```
46
+
47
+ ## Register for whitelist / presale
48
+
49
+ Users present a passport, then link a **claim wallet** (the address that receives presale access).
50
+
51
+ ```typescript
52
+ const client = new MedusaPassportClient({
53
+ baseUrl: "https://your-medusa-app.com",
54
+ apiKey: process.env.MEDUSA_PARTNER_API_KEY,
55
+ });
56
+
57
+ const registration = await client.register({
58
+ passport,
59
+ claimWallet: "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
60
+ campaignId: "my-presale-q3",
61
+ });
62
+
63
+ console.log(registration.claimWallet, registration.tierLabel);
64
+ ```
65
+
66
+ Pull the whitelist for your campaign:
67
+
68
+ ```typescript
69
+ const entries = await client.getWhitelist("my-presale-q3");
70
+ // [{ claimWallet, tier, tierLabel, nullifier, registeredAt }, ...]
71
+ ```
72
+
73
+ ## Partner API keys
74
+
75
+ Medusa operators create keys in `.env`:
76
+
77
+ ```env
78
+ MEDUSA_PARTNER_API_KEYS=my-presale-q3:sk_live_partner_key
79
+ ```
80
+
81
+ Each key is scoped to one `campaignId`.
82
+
83
+ ## Endpoints used by the SDK
84
+
85
+ | Endpoint | Purpose |
86
+ |----------|---------|
87
+ | `GET /api/passport/issuer` | Issuer public key |
88
+ | `GET /api/passport/verify` | Policy metadata |
89
+ | `POST /api/passport/verify` | Remote passport verification |
90
+ | `POST /api/partner/register` | Register passport + claim wallet |
91
+ | `GET /api/partner/whitelist?campaignId=` | Export campaign whitelist |
92
+
93
+ ## Tier gating example
94
+
95
+ ```typescript
96
+ const PRESALE_MIN_TIER = 1; // bronze
97
+ const ALLOCATION_BY_TIER = { 1: 0.1, 2: 0.5, 3: 2 };
98
+
99
+ const result = await client.verify(passport, { minTier: PRESALE_MIN_TIER });
100
+ if (!result.valid) throw new Error("Not eligible");
101
+
102
+ const maxSol = ALLOCATION_BY_TIER[result.tier!];
103
+ ```
104
+
105
+ ## Privacy model
106
+
107
+ - The passport proves eligibility **without** revealing the proving wallet.
108
+ - Registration links the passport to a **claim wallet** for whitelist/presale payout.
109
+ - One passport nullifier can register once per campaign (anti-sybil).
110
+
111
+ ## Browser + Node
112
+
113
+ The SDK works in Node.js, Next.js API routes, and the browser (local verify only).
@@ -0,0 +1,24 @@
1
+ import type { IssuerInfo, MedusaPassport, PassportPolicy, RegisterPassportInput, RegisterPassportResult, VerifyPassportOptions, VerifyPassportResult, WhitelistEntry } from "./types.js";
2
+ export interface MedusaPassportClientOptions {
3
+ baseUrl: string;
4
+ apiKey?: string;
5
+ issuerPublicKey?: string;
6
+ fetchImpl?: typeof fetch;
7
+ }
8
+ export declare class MedusaPassportClient {
9
+ private readonly baseUrl;
10
+ private readonly apiKey?;
11
+ private issuerPublicKey?;
12
+ private readonly fetchImpl;
13
+ constructor(options: MedusaPassportClientOptions);
14
+ fetchIssuer(): Promise<IssuerInfo>;
15
+ fetchPolicy(): Promise<PassportPolicy>;
16
+ verifyRemote(passport: MedusaPassport): Promise<VerifyPassportResult>;
17
+ verify(passport: MedusaPassport, options?: Omit<VerifyPassportOptions, "issuerPublicKey">): Promise<VerifyPassportResult>;
18
+ verifyLocal(passport: MedusaPassport, options: VerifyPassportOptions): VerifyPassportResult;
19
+ parseAndVerify(passportJson: string, options?: Omit<VerifyPassportOptions, "issuerPublicKey">): Promise<VerifyPassportResult>;
20
+ register(input: RegisterPassportInput): Promise<RegisterPassportResult>;
21
+ getWhitelist(campaignId: string): Promise<WhitelistEntry[]>;
22
+ hasValidSignature(passport: MedusaPassport, issuerPublicKey?: string): boolean;
23
+ }
24
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,OAAO,EAAE,2BAA2B;IAO1C,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC;IAelC,WAAW,IAAI,OAAO,CAAC,cAAc,CAAC;IAUtC,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA+BrE,MAAM,CACV,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE,IAAI,CAAC,qBAAqB,EAAE,iBAAiB,CAAM,GAC3D,OAAO,CAAC,oBAAoB,CAAC;IAehC,WAAW,CACT,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB;IAIjB,cAAc,CAClB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,IAAI,CAAC,qBAAqB,EAAE,iBAAiB,CAAM,GAC3D,OAAO,CAAC,oBAAoB,CAAC;IAiB1B,QAAQ,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA+BvE,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAgCjE,iBAAiB,CAAC,QAAQ,EAAE,cAAc,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO;CAQ/E"}
package/dist/client.js ADDED
@@ -0,0 +1,118 @@
1
+ import { MedusaPassportError } from "./errors.js";
2
+ import { parsePassportJson, verifyPassport, verifyPassportSignature, } from "./verify.js";
3
+ export class MedusaPassportClient {
4
+ constructor(options) {
5
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
6
+ this.apiKey = options.apiKey;
7
+ this.issuerPublicKey = options.issuerPublicKey;
8
+ this.fetchImpl = options.fetchImpl ?? fetch;
9
+ }
10
+ async fetchIssuer() {
11
+ const response = await this.fetchImpl(`${this.baseUrl}/api/passport/issuer`);
12
+ if (!response.ok) {
13
+ throw new MedusaPassportError("Failed to fetch issuer public key.", "API_ERROR");
14
+ }
15
+ const issuer = (await response.json());
16
+ this.issuerPublicKey = issuer.publicKey;
17
+ return issuer;
18
+ }
19
+ async fetchPolicy() {
20
+ const response = await this.fetchImpl(`${this.baseUrl}/api/passport/verify`);
21
+ if (!response.ok) {
22
+ throw new MedusaPassportError("Failed to fetch passport policy.", "API_ERROR");
23
+ }
24
+ return response.json();
25
+ }
26
+ async verifyRemote(passport) {
27
+ const response = await this.fetchImpl(`${this.baseUrl}/api/passport/verify`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify(passport),
31
+ });
32
+ const payload = (await response.json());
33
+ if (!response.ok || !payload.valid || !payload.passport) {
34
+ return {
35
+ valid: false,
36
+ errors: [payload.error ?? "Remote verification failed."],
37
+ };
38
+ }
39
+ return {
40
+ valid: true,
41
+ tier: payload.passport.statement.tier,
42
+ tierLabel: payload.tierLabel,
43
+ nullifier: payload.passport.nullifier,
44
+ expiresAt: payload.passport.expiresAt,
45
+ errors: [],
46
+ };
47
+ }
48
+ async verify(passport, options = {}) {
49
+ if (!this.issuerPublicKey) {
50
+ try {
51
+ await this.fetchIssuer();
52
+ }
53
+ catch {
54
+ return this.verifyRemote(passport);
55
+ }
56
+ }
57
+ return verifyPassport(passport, {
58
+ ...options,
59
+ issuerPublicKey: this.issuerPublicKey,
60
+ });
61
+ }
62
+ verifyLocal(passport, options) {
63
+ return verifyPassport(passport, options);
64
+ }
65
+ async parseAndVerify(passportJson, options = {}) {
66
+ const passport = parsePassportJson(passportJson);
67
+ if (!this.issuerPublicKey) {
68
+ await this.fetchIssuer().catch(() => undefined);
69
+ }
70
+ if (this.issuerPublicKey) {
71
+ return verifyPassport(passport, {
72
+ ...options,
73
+ issuerPublicKey: this.issuerPublicKey,
74
+ });
75
+ }
76
+ return this.verifyRemote(passport);
77
+ }
78
+ async register(input) {
79
+ if (!this.apiKey) {
80
+ throw new MedusaPassportError("apiKey is required to register passports.", "REGISTRATION_FAILED");
81
+ }
82
+ const response = await this.fetchImpl(`${this.baseUrl}/api/partner/register`, {
83
+ method: "POST",
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ Authorization: `Bearer ${this.apiKey}`,
87
+ },
88
+ body: JSON.stringify(input),
89
+ });
90
+ const payload = (await response.json());
91
+ if (!response.ok || !payload.registered) {
92
+ throw new MedusaPassportError(payload.error ?? "Passport registration failed.", "REGISTRATION_FAILED");
93
+ }
94
+ return payload;
95
+ }
96
+ async getWhitelist(campaignId) {
97
+ if (!this.apiKey) {
98
+ throw new MedusaPassportError("apiKey is required to fetch whitelist entries.", "API_ERROR");
99
+ }
100
+ const response = await this.fetchImpl(`${this.baseUrl}/api/partner/whitelist?campaignId=${encodeURIComponent(campaignId)}`, {
101
+ headers: {
102
+ Authorization: `Bearer ${this.apiKey}`,
103
+ },
104
+ });
105
+ const payload = (await response.json());
106
+ if (!response.ok) {
107
+ throw new MedusaPassportError(payload.error ?? "Failed to fetch whitelist.", "API_ERROR");
108
+ }
109
+ return payload.entries ?? [];
110
+ }
111
+ hasValidSignature(passport, issuerPublicKey) {
112
+ const key = issuerPublicKey ?? this.issuerPublicKey;
113
+ if (!key) {
114
+ return false;
115
+ }
116
+ return verifyPassportSignature(passport, key);
117
+ }
118
+ }
@@ -0,0 +1,5 @@
1
+ export declare class MedusaPassportError extends Error {
2
+ readonly code: "INVALID_JSON" | "INVALID_PASSPORT" | "VERIFY_FAILED" | "API_ERROR" | "REGISTRATION_FAILED";
3
+ constructor(message: string, code: "INVALID_JSON" | "INVALID_PASSPORT" | "VERIFY_FAILED" | "API_ERROR" | "REGISTRATION_FAILED");
4
+ }
5
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,mBAAoB,SAAQ,KAAK;aAG1B,IAAI,EAChB,cAAc,GACd,kBAAkB,GAClB,eAAe,GACf,WAAW,GACX,qBAAqB;gBANzB,OAAO,EAAE,MAAM,EACC,IAAI,EAChB,cAAc,GACd,kBAAkB,GAClB,eAAe,GACf,WAAW,GACX,qBAAqB;CAK5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,7 @@
1
+ export class MedusaPassportError extends Error {
2
+ constructor(message, code) {
3
+ super(message);
4
+ this.code = code;
5
+ this.name = "MedusaPassportError";
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ export { MedusaPassportClient } from "./client.js";
2
+ export type { MedusaPassportClientOptions } from "./client.js";
3
+ export { MedusaPassportError } from "./errors.js";
4
+ export { PASSPORT_POLICY_VERSION, PASSPORT_TIERS, TIER_LABELS, } from "./types.js";
5
+ export type { IssuerInfo, MedusaPassport, PassportPolicy, PassportPublicInputs, PassportStatement, PassportTier, RegisterPassportInput, RegisterPassportResult, VerifyPassportOptions, VerifyPassportResult, WhitelistEntry, ZkProofBundle, } from "./types.js";
6
+ export { hexToBytes, isValidSolanaAddress } from "./utils.js";
7
+ export { parsePassportJson, verifyPassport, verifyPassportSignature, } from "./verify.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,uBAAuB,GACxB,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { MedusaPassportClient } from "./client.js";
2
+ export { MedusaPassportError } from "./errors.js";
3
+ export { PASSPORT_POLICY_VERSION, PASSPORT_TIERS, TIER_LABELS, } from "./types.js";
4
+ export { hexToBytes, isValidSolanaAddress } from "./utils.js";
5
+ export { parsePassportJson, verifyPassport, verifyPassportSignature, } from "./verify.js";
@@ -0,0 +1,89 @@
1
+ export declare const PASSPORT_POLICY_VERSION: "medusa-passport-v1";
2
+ export declare const PASSPORT_TIERS: {
3
+ readonly BRONZE: 1;
4
+ readonly SILVER: 2;
5
+ readonly GOLD: 3;
6
+ };
7
+ export type PassportTier = (typeof PASSPORT_TIERS)[keyof typeof PASSPORT_TIERS];
8
+ export declare const TIER_LABELS: Record<PassportTier, string>;
9
+ export interface PassportPublicInputs {
10
+ current_timestamp: number;
11
+ min_age_seconds: number;
12
+ min_tx_count: number;
13
+ bronze_threshold: number;
14
+ silver_threshold: number;
15
+ gold_threshold: number;
16
+ }
17
+ export interface ZkProofBundle {
18
+ proofType: "noir_ultrahonk" | "dev_local";
19
+ proof: string;
20
+ publicInputs: string[];
21
+ }
22
+ export interface PassportStatement {
23
+ policyVersion: string;
24
+ tier: PassportTier;
25
+ tierLabel: string;
26
+ minWalletAgeDays: number;
27
+ minTransactionCount: number;
28
+ publicInputs: PassportPublicInputs;
29
+ }
30
+ export interface MedusaPassport {
31
+ type: "medusa_passport_v1";
32
+ chain: "solana";
33
+ statement: PassportStatement;
34
+ nullifier: string;
35
+ zkProof: ZkProofBundle;
36
+ issuedAt: string;
37
+ expiresAt: string;
38
+ issuer: "medusa";
39
+ signature: string;
40
+ }
41
+ export interface VerifyPassportOptions {
42
+ issuerPublicKey?: string;
43
+ minTier?: PassportTier;
44
+ policyVersion?: string;
45
+ now?: Date;
46
+ }
47
+ export interface VerifyPassportResult {
48
+ valid: boolean;
49
+ tier?: PassportTier;
50
+ tierLabel?: string;
51
+ nullifier?: string;
52
+ expiresAt?: string;
53
+ errors: string[];
54
+ }
55
+ export interface RegisterPassportInput {
56
+ passport: MedusaPassport;
57
+ claimWallet: string;
58
+ campaignId: string;
59
+ }
60
+ export interface RegisterPassportResult {
61
+ registered: boolean;
62
+ campaignId: string;
63
+ claimWallet: string;
64
+ tier: PassportTier;
65
+ tierLabel: string;
66
+ nullifier: string;
67
+ registeredAt: string;
68
+ }
69
+ export interface WhitelistEntry {
70
+ campaignId: string;
71
+ claimWallet: string;
72
+ tier: PassportTier;
73
+ tierLabel: string;
74
+ nullifier: string;
75
+ registeredAt: string;
76
+ }
77
+ export interface PassportPolicy {
78
+ policyVersion: string;
79
+ requirements: Record<string, unknown>;
80
+ validityDays: number;
81
+ issuePriceLamports: number;
82
+ devMode: boolean;
83
+ }
84
+ export interface IssuerInfo {
85
+ issuer: "medusa";
86
+ publicKey: string;
87
+ policyVersion: string;
88
+ }
89
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ export const PASSPORT_POLICY_VERSION = "medusa-passport-v1";
2
+ export const PASSPORT_TIERS = {
3
+ BRONZE: 1,
4
+ SILVER: 2,
5
+ GOLD: 3,
6
+ };
7
+ export const TIER_LABELS = {
8
+ [PASSPORT_TIERS.BRONZE]: "BRONZE",
9
+ [PASSPORT_TIERS.SILVER]: "SILVER",
10
+ [PASSPORT_TIERS.GOLD]: "GOLD",
11
+ };
@@ -0,0 +1,3 @@
1
+ export declare function hexToBytes(hex: string): Uint8Array;
2
+ export declare function isValidSolanaAddress(address: string): boolean;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAgBlD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE7D"}
package/dist/utils.js ADDED
@@ -0,0 +1,14 @@
1
+ export function hexToBytes(hex) {
2
+ const normalized = hex.startsWith("0x") ? hex.slice(2) : hex;
3
+ if (normalized.length % 2 !== 0) {
4
+ throw new Error("Invalid hex string.");
5
+ }
6
+ const bytes = new Uint8Array(normalized.length / 2);
7
+ for (let index = 0; index < bytes.length; index += 1) {
8
+ bytes[index] = Number.parseInt(normalized.slice(index * 2, index * 2 + 2), 16);
9
+ }
10
+ return bytes;
11
+ }
12
+ export function isValidSolanaAddress(address) {
13
+ return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
14
+ }
@@ -0,0 +1,5 @@
1
+ import { type MedusaPassport, type VerifyPassportOptions, type VerifyPassportResult } from "./types.js";
2
+ export declare function parsePassportJson(input: string): MedusaPassport;
3
+ export declare function verifyPassportSignature(passport: MedusaPassport, issuerPublicKey: string): boolean;
4
+ export declare function verifyPassport(passport: MedusaPassport, options?: VerifyPassportOptions): VerifyPassportResult;
5
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAoBpB,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAc/D;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,cAAc,EACxB,eAAe,EAAE,MAAM,GACtB,OAAO,CAST;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CA8CtB"}
package/dist/verify.js ADDED
@@ -0,0 +1,69 @@
1
+ import nacl from "tweetnacl";
2
+ import { PASSPORT_POLICY_VERSION, TIER_LABELS, } from "./types.js";
3
+ import { hexToBytes } from "./utils.js";
4
+ function isPassportShape(value) {
5
+ if (!value || typeof value !== "object") {
6
+ return false;
7
+ }
8
+ const passport = value;
9
+ return (passport.type === "medusa_passport_v1" &&
10
+ passport.chain === "solana" &&
11
+ passport.issuer === "medusa" &&
12
+ typeof passport.signature === "string" &&
13
+ typeof passport.nullifier === "string" &&
14
+ typeof passport.expiresAt === "string" &&
15
+ typeof passport.statement?.tier === "number");
16
+ }
17
+ export function parsePassportJson(input) {
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(input);
21
+ }
22
+ catch {
23
+ throw new Error("Passport JSON is invalid.");
24
+ }
25
+ if (!isPassportShape(parsed)) {
26
+ throw new Error("Passport structure is invalid.");
27
+ }
28
+ return parsed;
29
+ }
30
+ export function verifyPassportSignature(passport, issuerPublicKey) {
31
+ const { signature, ...payload } = passport;
32
+ const message = new TextEncoder().encode(JSON.stringify(payload));
33
+ return nacl.sign.detached.verify(message, hexToBytes(signature), hexToBytes(issuerPublicKey));
34
+ }
35
+ export function verifyPassport(passport, options = {}) {
36
+ const errors = [];
37
+ const policyVersion = options.policyVersion ?? PASSPORT_POLICY_VERSION;
38
+ const now = options.now ?? new Date();
39
+ if (passport.type !== "medusa_passport_v1") {
40
+ errors.push("Unsupported passport type.");
41
+ }
42
+ if (passport.statement.policyVersion !== policyVersion) {
43
+ errors.push("Passport policy version mismatch.");
44
+ }
45
+ if (new Date(passport.expiresAt).getTime() < now.getTime()) {
46
+ errors.push("Passport has expired.");
47
+ }
48
+ if (options.minTier && passport.statement.tier < options.minTier) {
49
+ errors.push(`Passport tier ${passport.statement.tier} is below required tier ${options.minTier}.`);
50
+ }
51
+ if (options.issuerPublicKey) {
52
+ const signatureValid = verifyPassportSignature(passport, options.issuerPublicKey);
53
+ if (!signatureValid) {
54
+ errors.push("Passport signature is invalid.");
55
+ }
56
+ }
57
+ else {
58
+ errors.push("issuerPublicKey is required for local verification.");
59
+ }
60
+ const valid = errors.length === 0;
61
+ return {
62
+ valid,
63
+ tier: passport.statement.tier,
64
+ tierLabel: TIER_LABELS[passport.statement.tier],
65
+ nullifier: passport.nullifier,
66
+ expiresAt: passport.expiresAt,
67
+ errors,
68
+ };
69
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@zkmedusa/passport-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Verify and register Medusa Passports in third-party apps, presales, and whitelists.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "homepage": "https://www.zkmedusa.com/docs",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "dependencies": {
28
+ "tweetnacl": "^1.0.3"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.7.2"
32
+ },
33
+ "keywords": [
34
+ "medusa",
35
+ "passport",
36
+ "solana",
37
+ "zk",
38
+ "whitelist",
39
+ "presale"
40
+ ]
41
+ }