UNPKG

@reclaimprotocol/js-sdk

Version:

Designed to request proofs from the Reclaim protocol and manage the flow of claims and witness interactions.

1,568 lines (1,544 loc) 85.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // package.json var require_package = __commonJS({ "package.json"(exports2, module2) { module2.exports = { name: "@reclaimprotocol/js-sdk", version: "4.5.1", description: "Designed to request proofs from the Reclaim protocol and manage the flow of claims and witness interactions.", main: "dist/index.js", types: "dist/index.d.ts", keywords: [ "reclaim", "protocol", "blockchain", "proof", "verification", "identity", "claims", "witness", "sdk", "javascript", "typescript", "decentralized", "web3" ], files: [ "dist" ], tsup: { entry: [ "src/index.ts" ], splitting: false, sourcemap: true, clean: true }, scripts: { build: "sh scripts/build.sh", release: "release-it", test: "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", commitlint: "commitlint --edit" }, repository: { type: "git", url: "https://github.com/reclaimprotocol/reclaim-js-sdk" }, author: "ali <ali@creatoros.co>", license: "See License in <https://github.com/reclaimprotocol/.github/blob/main/LICENSE>", bugs: { url: "https://github.com/reclaimprotocol/reclaim-js-sdk/issues" }, homepage: "https://github.com/reclaimprotocol/reclaim-js-sdk/", publishConfig: { registry: "https://registry.npmjs.org/", access: "public" }, "release-it": { git: { commitMessage: "chore: release ${version}", tagName: "v${version}" }, npm: { publish: true, tag: "latest" }, github: { release: true }, plugins: { "@release-it/conventional-changelog": { preset: "angular" } } }, devDependencies: { "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", "@types/jest": "^30.0.0", "@types/qs": "^6.9.11", "@types/url-parse": "^1.4.11", "@types/uuid": "^9.0.7", jest: "^30.1.3", "jest-environment-jsdom": "^30.1.2", "ts-jest": "^29.4.1", tsup: "^8.0.1", typescript: "^5.3.3" }, dependencies: { "@release-it/conventional-changelog": "^10.0.1", canonicalize: "^2.0.0", ethers: "^6.9.1", qs: "^6.11.2", "release-it": "^19.0.4", "url-parse": "^1.5.10", uuid: "^9.0.1" } }; } }); // src/index.ts var index_exports = {}; __export(index_exports, { ClaimCreationType: () => ClaimCreationType, DeviceType: () => DeviceType, RECLAIM_EXTENSION_ACTIONS: () => RECLAIM_EXTENSION_ACTIONS, ReclaimProofRequest: () => ReclaimProofRequest, clearDeviceCache: () => clearDeviceCache, getDeviceType: () => getDeviceType, getMobileDeviceType: () => getMobileDeviceType, isDesktopDevice: () => isDesktopDevice, isMobileDevice: () => isMobileDevice, transformForOnchain: () => transformForOnchain, verifyProof: () => verifyProof }); module.exports = __toCommonJS(index_exports); // src/utils/interfaces.ts var RECLAIM_EXTENSION_ACTIONS = { CHECK_EXTENSION: "RECLAIM_EXTENSION_CHECK", EXTENSION_RESPONSE: "RECLAIM_EXTENSION_RESPONSE", START_VERIFICATION: "RECLAIM_START_VERIFICATION", STATUS_UPDATE: "RECLAIM_STATUS_UPDATE" }; // src/witness.ts var import_ethers = require("ethers"); function fetchWitnessListForClaim({ witnesses, witnessesRequiredForClaim, epoch }, params, timestampS) { const identifier = typeof params === "string" ? params : getIdentifierFromClaimInfo(params); const completeInput = [ identifier, epoch.toString(), witnessesRequiredForClaim.toString(), timestampS.toString() ].join("\n"); const completeHashStr = import_ethers.ethers.keccak256(strToUint8Array(completeInput)); const completeHash = import_ethers.ethers.getBytes(completeHashStr); const completeHashView = uint8ArrayToDataView(completeHash); const witnessesLeft = [...witnesses]; const selectedWitnesses = []; let byteOffset = 0; for (let i = 0; i < witnessesRequiredForClaim; i++) { const randomSeed = completeHashView.getUint32(byteOffset); const witnessIndex = randomSeed % witnessesLeft.length; const witness = witnessesLeft[witnessIndex]; selectedWitnesses.push(witness); witnessesLeft[witnessIndex] = witnessesLeft[witnessesLeft.length - 1]; witnessesLeft.pop(); byteOffset = (byteOffset + 4) % completeHash.length; } return selectedWitnesses; } function getIdentifierFromClaimInfo(info) { const str = `${info.provider} ${info.parameters} ${info.context || ""}`; return import_ethers.ethers.keccak256(strToUint8Array(str)).toLowerCase(); } function strToUint8Array(str) { return new TextEncoder().encode(str); } function uint8ArrayToDataView(arr) { return new DataView(arr.buffer, arr.byteOffset, arr.byteLength); } function createSignDataForClaim(data) { const identifier = "identifier" in data ? data.identifier : getIdentifierFromClaimInfo(data); const lines = [ identifier, data.owner.toLowerCase(), data.timestampS.toString(), data.epoch.toString() ]; return lines.join("\n"); } // src/utils/types.ts var ClaimCreationType = /* @__PURE__ */ ((ClaimCreationType2) => { ClaimCreationType2["STANDALONE"] = "createClaim"; ClaimCreationType2["ON_ME_CHAIN"] = "createClaimOnMechain"; return ClaimCreationType2; })(ClaimCreationType || {}); var DeviceType = /* @__PURE__ */ ((DeviceType2) => { DeviceType2["ANDROID"] = "android"; DeviceType2["IOS"] = "ios"; DeviceType2["DESKTOP"] = "desktop"; DeviceType2["MOBILE"] = "mobile"; return DeviceType2; })(DeviceType || {}); // src/Reclaim.ts var import_ethers6 = require("ethers"); var import_canonicalize2 = __toESM(require("canonicalize")); // src/utils/errors.ts function createErrorClass(name) { return class extends Error { constructor(message, innerError) { const fullMessage = innerError ? `${message || ""} caused by ${innerError.name}: ${innerError.message}` : message; super(fullMessage); this.innerError = innerError; this.name = name; if (innerError) { this.stack += ` Caused by: ${innerError.stack}`; } } }; } var TimeoutError = createErrorClass("TimeoutError"); var ProofNotVerifiedError = createErrorClass("ProofNotVerifiedError"); var SessionNotStartedError = createErrorClass("SessionNotStartedError"); var ProviderNotFoundError = createErrorClass("ProviderNotFoundError"); var SignatureGeneratingError = createErrorClass("SignatureGeneratingError"); var SignatureNotFoundError = createErrorClass("SignatureNotFoundError"); var InvalidSignatureError = createErrorClass("InvalidSignatureError"); var UpdateSessionError = createErrorClass("UpdateSessionError"); var InitSessionError = createErrorClass("InitSessionError"); var ProviderFailedError = createErrorClass("ProviderFailedError"); var InvalidParamError = createErrorClass("InvalidParamError"); var ApplicationError = createErrorClass("ApplicationError"); var InitError = createErrorClass("InitError"); var BackendServerError = createErrorClass("BackendServerError"); var GetStatusUrlError = createErrorClass("GetStatusUrlError"); var NoProviderParamsError = createErrorClass("NoProviderParamsError"); var SetParamsError = createErrorClass("SetParamsError"); var AddContextError = createErrorClass("AddContextError"); var SetSignatureError = createErrorClass("SetSignatureError"); var GetAppCallbackUrlError = createErrorClass("GetAppCallbackUrlError"); var StatusUrlError = createErrorClass("StatusUrlError"); var InavlidParametersError = createErrorClass("InavlidParametersError"); var ProofSubmissionFailedError = createErrorClass("ProofSubmissionFailedError"); // src/utils/logger.ts var SimpleLogger = class { constructor() { this.level = "info"; } setLevel(level) { this.level = level; } shouldLog(messageLevel) { const levels = ["error", "warn", "info", "silent"]; return levels.indexOf(this.level) >= levels.indexOf(messageLevel); } log(level, message, ...args) { if (this.shouldLog(level) && this.level !== "silent") { const logFunction = this.getLogFunction(level); console.log("current level", this.level); logFunction(`[${level.toUpperCase()}]`, message, ...args); } } getLogFunction(level) { switch (level) { case "error": return console.error; case "warn": return console.warn; case "info": return console.info; default: return () => { }; } } info(message, ...args) { this.log("info", message, ...args); } warn(message, ...args) { this.log("warn", message, ...args); } error(message, ...args) { this.log("error", message, ...args); } }; var logger = new SimpleLogger(); function setLogLevel(level) { logger.setLevel(level); } var logger_default = { logger, setLogLevel }; // src/utils/helper.ts var logger2 = logger_default.logger; function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function replaceAll(str, find, replace) { if (find === "") return str; return str.replace(new RegExp(escapeRegExp(find), "g"), replace); } function scheduleIntervalEndingTask(sessionId, intervals, onFailureCallback, timeout = 1e3 * 60 * 10) { setTimeout(() => { if (intervals.has(sessionId)) { const message = "Interval ended without receiving proofs"; onFailureCallback(new TimeoutError(message)); logger2.info(message); clearInterval(intervals.get(sessionId)); intervals.delete(sessionId); } }, timeout); } // src/utils/constants.ts var BACKEND_BASE_URL = "https://api.reclaimprotocol.org"; function setBackendBaseUrl(url) { BACKEND_BASE_URL = url; } var constants = { // Default callback URL for Reclaim protocol get DEFAULT_RECLAIM_CALLBACK_URL() { return `${BACKEND_BASE_URL}/api/sdk/callback?callbackId=`; }, // Default status URL for Reclaim sessions get DEFAULT_RECLAIM_STATUS_URL() { return `${BACKEND_BASE_URL}/api/sdk/session/`; }, // URL for sharing Reclaim templates RECLAIM_SHARE_URL: "https://share.reclaimprotocol.org/verifier/?template=", // Chrome extension URL for Reclaim Protocol CHROME_EXTENSION_URL: "https://chromewebstore.google.com/detail/reclaim-extension/oafieibbbcepkmenknelhmgaoahamdeh", // QR Code API base URL QR_CODE_API_URL: "https://api.qrserver.com/v1/create-qr-code/" }; // src/utils/validationUtils.ts var import_ethers2 = require("ethers"); var import_canonicalize = __toESM(require("canonicalize")); var logger3 = logger_default.logger; function validateFunctionParams(params, functionName) { params.forEach(({ input, paramName, isString }) => { if (input == null) { logger3.info(`Validation failed: ${paramName} in ${functionName} is null or undefined`); throw new InvalidParamError(`${paramName} passed to ${functionName} must not be null or undefined.`); } if (isString && typeof input !== "string") { logger3.info(`Validation failed: ${paramName} in ${functionName} is not a string`); throw new InvalidParamError(`${paramName} passed to ${functionName} must be a string.`); } if (isString && input.trim() === "") { logger3.info(`Validation failed: ${paramName} in ${functionName} is an empty string`); throw new InvalidParamError(`${paramName} passed to ${functionName} must not be an empty string.`); } }); } function validateParameters(parameters) { try { if (typeof parameters !== "object" || parameters === null) { logger3.info(`Parameters validation failed: Provided parameters is not an object`); throw new InavlidParametersError(`The provided parameters is not an object`); } for (const [key, value] of Object.entries(parameters)) { if (typeof key !== "string" || typeof value !== "string") { logger3.info(`Parameters validation failed: Provided parameters is not an object of key value pairs of string and string`); throw new InavlidParametersError(`The provided parameters is not an object of key value pairs of string and string`); } } } catch (e) { logger3.info(`Parameters validation failed: ${e.message}`); throw new InavlidParametersError(`Invalid parameters passed to validateParameters.`, e); } } function validateURL(url, functionName) { try { new URL(url); } catch (e) { logger3.info(`URL validation failed for ${url} in ${functionName}: ${e.message}`); throw new InvalidParamError(`Invalid URL format ${url} passed to ${functionName}.`, e); } } function validateSignature(providerId, signature, applicationId, timestamp) { try { logger3.info(`Starting signature validation for providerId: ${providerId}, applicationId: ${applicationId}, timestamp: ${timestamp}`); const message = (0, import_canonicalize.default)({ providerId, timestamp }); if (!message) { logger3.info("Failed to canonicalize message for signature validation"); throw new Error("Failed to canonicalize message"); } const messageHash = import_ethers2.ethers.keccak256(new TextEncoder().encode(message)); let appId = import_ethers2.ethers.verifyMessage( import_ethers2.ethers.getBytes(messageHash), import_ethers2.ethers.hexlify(signature) ).toLowerCase(); if (import_ethers2.ethers.getAddress(appId) !== import_ethers2.ethers.getAddress(applicationId)) { logger3.info(`Signature validation failed: Mismatch between derived appId (${appId}) and provided applicationId (${applicationId})`); throw new InvalidSignatureError(`Signature does not match the application id: ${appId}`); } logger3.info(`Signature validated successfully for applicationId: ${applicationId}`); } catch (err) { logger3.info(`Signature validation failed: ${err.message}`); if (err instanceof InvalidSignatureError) { throw err; } throw new InvalidSignatureError(`Failed to validate signature: ${err.message}`); } } function validateContext(context) { if (!context.contextAddress) { logger3.info(`Context validation failed: Provided context address in context is not valid`); throw new InvalidParamError(`The provided context address in context is not valid`); } if (!context.contextMessage) { logger3.info(`Context validation failed: Provided context message in context is not valid`); throw new InvalidParamError(`The provided context message in context is not valid`); } validateFunctionParams([ { input: context.contextAddress, paramName: "contextAddress", isString: true }, { input: context.contextMessage, paramName: "contextMessage", isString: true } ], "validateContext"); } function validateModalOptions(modalOptions, functionName, paramPrefix = "") { if (modalOptions.title !== void 0) { validateFunctionParams([ { input: modalOptions.title, paramName: `${paramPrefix}title`, isString: true } ], functionName); } if (modalOptions.description !== void 0) { validateFunctionParams([ { input: modalOptions.description, paramName: `${paramPrefix}description`, isString: true } ], functionName); } if (modalOptions.extensionUrl !== void 0) { validateURL(modalOptions.extensionUrl, functionName); validateFunctionParams([ { input: modalOptions.extensionUrl, paramName: `${paramPrefix}extensionUrl`, isString: true } ], functionName); } if (modalOptions.darkTheme !== void 0) { if (typeof modalOptions.darkTheme !== "boolean") { throw new InvalidParamError(`${paramPrefix}darkTheme prop must be a boolean`); } validateFunctionParams([ { input: modalOptions.darkTheme, paramName: `${paramPrefix}darkTheme` } ], functionName); } if (modalOptions.modalPopupTimer !== void 0) { if (typeof modalOptions.modalPopupTimer !== "number" || modalOptions.modalPopupTimer <= 0 || !Number.isInteger(modalOptions.modalPopupTimer)) { throw new InvalidParamError(`${paramPrefix}modalPopupTimer prop must be a valid time in minutes`); } validateFunctionParams([ { input: modalOptions.modalPopupTimer, paramName: `${paramPrefix}modalPopupTimer` } ], functionName); } if (modalOptions.showExtensionInstallButton !== void 0) { if (typeof modalOptions.showExtensionInstallButton !== "boolean") { throw new InvalidParamError(`${paramPrefix}showExtensionInstallButton prop must be a boolean`); } validateFunctionParams([ { input: modalOptions.showExtensionInstallButton, paramName: `${paramPrefix}showExtensionInstallButton` } ], functionName); } } // src/utils/sessionUtils.ts var logger4 = logger_default.logger; function initSession(providerId, appId, timestamp, signature, versionNumber) { return __async(this, null, function* () { logger4.info(`Initializing session for providerId: ${providerId}, appId: ${appId}`); try { const response = yield fetch(`${BACKEND_BASE_URL}/api/sdk/init/session/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerId, appId, timestamp, signature, versionNumber }) }); const res = yield response.json(); if (!response.ok) { logger4.info(`Session initialization failed: ${res.message || "Unknown error"}`); throw new InitSessionError(res.message || `Error initializing session with providerId: ${providerId}`); } return res; } catch (err) { logger4.info(`Failed to initialize session for providerId: ${providerId}, appId: ${appId}`, err); throw err; } }); } function updateSession(sessionId, status) { return __async(this, null, function* () { logger4.info(`Updating session status for sessionId: ${sessionId}, new status: ${status}`); validateFunctionParams( [{ input: sessionId, paramName: "sessionId", isString: true }], "updateSession" ); try { const response = yield fetch(`${BACKEND_BASE_URL}/api/sdk/update/session/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId, status }) }); const res = yield response.json(); if (!response.ok) { const errorMessage = `Error updating session with sessionId: ${sessionId}. Status Code: ${response.status}`; logger4.info(errorMessage, res); throw new UpdateSessionError(errorMessage); } logger4.info(`Session status updated successfully for sessionId: ${sessionId}`); return res; } catch (err) { const errorMessage = `Failed to update session with sessionId: ${sessionId}`; logger4.info(errorMessage, err); throw new UpdateSessionError(`Error updating session with sessionId: ${sessionId}`); } }); } function fetchStatusUrl(sessionId) { return __async(this, null, function* () { validateFunctionParams( [{ input: sessionId, paramName: "sessionId", isString: true }], "fetchStatusUrl" ); try { const response = yield fetch(`${constants.DEFAULT_RECLAIM_STATUS_URL}${sessionId}`, { method: "GET", headers: { "Content-Type": "application/json" } }); const res = yield response.json(); if (!response.ok) { const errorMessage = `Error fetching status URL for sessionId: ${sessionId}. Status Code: ${response.status}`; logger4.info(errorMessage, res); throw new StatusUrlError(errorMessage); } return res; } catch (err) { const errorMessage = `Failed to fetch status URL for sessionId: ${sessionId}`; logger4.info(errorMessage, err); throw new StatusUrlError(`Error fetching status URL for sessionId: ${sessionId}`); } }); } // src/utils/proofUtils.ts var import_ethers5 = require("ethers"); // src/contract-types/contracts/factories/Reclaim__factory.ts var import_ethers3 = require("ethers"); var _abi = [ { anonymous: false, inputs: [ { indexed: false, internalType: "address", name: "previousAdmin", type: "address" }, { indexed: false, internalType: "address", name: "newAdmin", type: "address" } ], name: "AdminChanged", type: "event" }, { anonymous: false, inputs: [ { indexed: true, internalType: "address", name: "beacon", type: "address" } ], name: "BeaconUpgraded", type: "event" }, { anonymous: false, inputs: [ { components: [ { internalType: "uint32", name: "id", type: "uint32" }, { internalType: "uint32", name: "timestampStart", type: "uint32" }, { internalType: "uint32", name: "timestampEnd", type: "uint32" }, { components: [ { internalType: "address", name: "addr", type: "address" }, { internalType: "string", name: "host", type: "string" } ], internalType: "struct Reclaim.Witness[]", name: "witnesses", type: "tuple[]" }, { internalType: "uint8", name: "minimumWitnessesForClaimCreation", type: "uint8" } ], indexed: false, internalType: "struct Reclaim.Epoch", name: "epoch", type: "tuple" } ], name: "EpochAdded", type: "event" }, { anonymous: false, inputs: [ { indexed: false, internalType: "uint8", name: "version", type: "uint8" } ], name: "Initialized", type: "event" }, { anonymous: false, inputs: [ { indexed: true, internalType: "address", name: "previousOwner", type: "address" }, { indexed: true, internalType: "address", name: "newOwner", type: "address" } ], name: "OwnershipTransferred", type: "event" }, { anonymous: false, inputs: [ { indexed: true, internalType: "address", name: "implementation", type: "address" } ], name: "Upgraded", type: "event" }, { inputs: [ { internalType: "address", name: "witnessAddress", type: "address" }, { internalType: "string", name: "host", type: "string" } ], name: "addAsWitness", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [], name: "addNewEpoch", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [ { internalType: "uint32", name: "epochNum", type: "uint32" }, { components: [ { internalType: "string", name: "provider", type: "string" }, { internalType: "string", name: "parameters", type: "string" }, { internalType: "string", name: "context", type: "string" } ], internalType: "struct Claims.ClaimInfo", name: "claimInfo", type: "tuple" }, { components: [ { internalType: "bytes32", name: "identifier", type: "bytes32" }, { internalType: "address", name: "owner", type: "address" }, { internalType: "uint32", name: "timestampS", type: "uint32" }, { internalType: "uint256", name: "epoch", type: "uint256" } ], internalType: "struct Claims.CompleteClaimData", name: "claimData", type: "tuple" }, { internalType: "bytes[]", name: "signatures", type: "bytes[]" } ], name: "assertValidEpochAndSignedClaim", outputs: [], stateMutability: "view", type: "function" }, { inputs: [], name: "currentEpoch", outputs: [ { internalType: "uint32", name: "", type: "uint32" } ], stateMutability: "view", type: "function" }, { inputs: [], name: "epochDurationS", outputs: [ { internalType: "uint32", name: "", type: "uint32" } ], stateMutability: "view", type: "function" }, { inputs: [ { internalType: "uint256", name: "", type: "uint256" } ], name: "epochs", outputs: [ { internalType: "uint32", name: "id", type: "uint32" }, { internalType: "uint32", name: "timestampStart", type: "uint32" }, { internalType: "uint32", name: "timestampEnd", type: "uint32" }, { internalType: "uint8", name: "minimumWitnessesForClaimCreation", type: "uint8" } ], stateMutability: "view", type: "function" }, { inputs: [ { internalType: "uint32", name: "epoch", type: "uint32" } ], name: "fetchEpoch", outputs: [ { components: [ { internalType: "uint32", name: "id", type: "uint32" }, { internalType: "uint32", name: "timestampStart", type: "uint32" }, { internalType: "uint32", name: "timestampEnd", type: "uint32" }, { components: [ { internalType: "address", name: "addr", type: "address" }, { internalType: "string", name: "host", type: "string" } ], internalType: "struct Reclaim.Witness[]", name: "witnesses", type: "tuple[]" }, { internalType: "uint8", name: "minimumWitnessesForClaimCreation", type: "uint8" } ], internalType: "struct Reclaim.Epoch", name: "", type: "tuple" } ], stateMutability: "view", type: "function" }, { inputs: [ { internalType: "uint32", name: "epoch", type: "uint32" }, { internalType: "bytes32", name: "identifier", type: "bytes32" }, { internalType: "uint32", name: "timestampS", type: "uint32" } ], name: "fetchWitnessesForClaim", outputs: [ { components: [ { internalType: "address", name: "addr", type: "address" }, { internalType: "string", name: "host", type: "string" } ], internalType: "struct Reclaim.Witness[]", name: "", type: "tuple[]" } ], stateMutability: "view", type: "function" }, { inputs: [], name: "initialize", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [], name: "minimumWitnessesForClaimCreation", outputs: [ { internalType: "uint8", name: "", type: "uint8" } ], stateMutability: "view", type: "function" }, { inputs: [], name: "owner", outputs: [ { internalType: "address", name: "", type: "address" } ], stateMutability: "view", type: "function" }, { inputs: [], name: "proxiableUUID", outputs: [ { internalType: "bytes32", name: "", type: "bytes32" } ], stateMutability: "view", type: "function" }, { inputs: [ { internalType: "address", name: "witnessAddress", type: "address" } ], name: "removeAsWitness", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [], name: "renounceOwnership", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [ { internalType: "address", name: "newOwner", type: "address" } ], name: "transferOwnership", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [ { internalType: "address", name: "addr", type: "address" }, { internalType: "bool", name: "isWhitelisted", type: "bool" } ], name: "updateWitnessWhitelist", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [ { internalType: "address", name: "newImplementation", type: "address" } ], name: "upgradeTo", outputs: [], stateMutability: "nonpayable", type: "function" }, { inputs: [ { internalType: "address", name: "newImplementation", type: "address" }, { internalType: "bytes", name: "data", type: "bytes" } ], name: "upgradeToAndCall", outputs: [], stateMutability: "payable", type: "function" }, { inputs: [ { internalType: "uint256", name: "", type: "uint256" } ], name: "witnesses", outputs: [ { internalType: "address", name: "addr", type: "address" }, { internalType: "string", name: "host", type: "string" } ], stateMutability: "view", type: "function" } ]; var Reclaim__factory = class { static connect(address, signerOrProvider) { return new import_ethers3.Contract(address, _abi, signerOrProvider); } }; Reclaim__factory.abi = _abi; // src/contract-types/config.json var config_default = { "0x1a4": { chainName: "opt-goerli", address: "0xF93F605142Fb1Efad7Aa58253dDffF67775b4520", rpcUrl: "https://opt-goerli.g.alchemy.com/v2/rksDkSUXd2dyk2ANy_zzODknx_AAokui" }, "0xaa37dc": { chainName: "opt-sepolia", address: "0x6D0f81BDA11995f25921aAd5B43359630E65Ca96", rpcUrl: "https://opt-sepolia.g.alchemy.com/v2/aO1-SfG4oFRLyAiLREqzyAUu0HTCwHgs" } }; // src/smart-contract.ts var import_ethers4 = require("ethers"); var DEFAULT_CHAIN_ID = 11155420; function makeBeacon(chainId) { chainId = chainId || DEFAULT_CHAIN_ID; const contract = getContract(chainId); if (contract) { let _a; return makeBeaconCacheable({ getState(epochId) { return __async(this, null, function* () { const epoch = yield contract.fetchEpoch(epochId || 0); if (!epoch.id) { throw new Error(`Invalid epoch ID: ${epochId}`); } return { epoch: epoch.id, witnesses: epoch.witnesses.map((w) => ({ id: w.addr.toLowerCase(), url: w.host })), witnessesRequiredForClaim: epoch.minimumWitnessesForClaimCreation, nextEpochTimestampS: epoch.timestampEnd }; }); } }); } else { return void 0; } } function makeBeaconCacheable(beacon) { const cache = {}; return __spreadProps(__spreadValues({}, beacon), { getState(epochId) { return __async(this, null, function* () { if (!epochId) { const state = yield beacon.getState(); return state; } const key = epochId; if (!cache[key]) { cache[key] = beacon.getState(epochId); } return cache[key]; }); } }); } var existingContractsMap = {}; function getContract(chainId) { const chainKey = `0x${chainId.toString(16)}`; if (!existingContractsMap[chainKey]) { const contractData = config_default[chainKey]; if (!contractData) { throw new Error(`Unsupported chain: "${chainKey}"`); } const rpcProvider = new import_ethers4.ethers.JsonRpcProvider(contractData.rpcUrl); existingContractsMap[chainKey] = Reclaim__factory.connect( contractData.address, rpcProvider ); } return existingContractsMap[chainKey]; } // src/utils/proofUtils.ts var logger5 = logger_default.logger; function getShortenedUrl(url) { return __async(this, null, function* () { logger5.info(`Attempting to shorten URL: ${url}`); try { validateURL(url, "getShortenedUrl"); const response = yield fetch(`${BACKEND_BASE_URL}/api/sdk/shortener`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fullUrl: url }) }); const res = yield response.json(); if (!response.ok) { logger5.info(`Failed to shorten URL: ${url}, Response: ${JSON.stringify(res)}`); return url; } const shortenedVerificationUrl = res.result.shortUrl; return shortenedVerificationUrl; } catch (err) { logger5.info(`Error shortening URL: ${url}, Error: ${err}`); return url; } }); } function createLinkWithTemplateData(templateData, sharePagePath) { return __async(this, null, function* () { let template = encodeURIComponent(JSON.stringify(templateData)); template = replaceAll(template, "(", "%28"); template = replaceAll(template, ")", "%29"); const fullLink = sharePagePath ? `${sharePagePath}/?template=${template}` : `${constants.RECLAIM_SHARE_URL}${template}`; try { const shortenedLink = yield getShortenedUrl(fullLink); return shortenedLink; } catch (err) { logger5.info(`Error creating link for sessionId: ${templateData.sessionId}, Error: ${err}`); return fullLink; } }); } function getWitnessesForClaim(epoch, identifier, timestampS) { return __async(this, null, function* () { const beacon = makeBeacon(); if (!beacon) { logger5.info("No beacon available for getting witnesses"); throw new Error("No beacon available"); } const state = yield beacon.getState(epoch); const witnessList = fetchWitnessListForClaim(state, identifier, timestampS); const witnesses = witnessList.map((w) => w.id.toLowerCase()); return witnesses; }); } function recoverSignersOfSignedClaim({ claim, signatures }) { const dataStr = createSignDataForClaim(__spreadValues({}, claim)); const signers = signatures.map( (signature) => import_ethers5.ethers.verifyMessage(dataStr, import_ethers5.ethers.hexlify(signature)).toLowerCase() ); return signers; } function assertValidSignedClaim(claim, expectedWitnessAddresses) { const witnessAddresses = recoverSignersOfSignedClaim(claim); const witnessesNotSeen = new Set(expectedWitnessAddresses); for (const witness of witnessAddresses) { if (witnessesNotSeen.has(witness)) { witnessesNotSeen.delete(witness); } } if (witnessesNotSeen.size > 0) { const missingWitnesses = Array.from(witnessesNotSeen).join(", "); logger5.info(`Claim validation failed. Missing signatures from: ${missingWitnesses}`); throw new ProofNotVerifiedError( `Missing signatures from ${missingWitnesses}` ); } } // src/utils/modalUtils.ts var logger6 = logger_default.logger; var QRCodeModal = class { constructor(options = {}) { this.countdownSeconds = 60; this.modalId = "reclaim-qr-modal"; this.options = __spreadValues({ title: "Verify with Reclaim", description: "Scan the QR code with your mobile device to complete verification", extensionUrl: constants.CHROME_EXTENSION_URL, darkTheme: false, modalPopupTimer: 1, // default to 1 minute showExtensionInstallButton: false }, options); } show(requestUrl) { return __async(this, null, function* () { try { this.close(); const modalHTML = this.createModalHTML(); document.body.insertAdjacentHTML("beforeend", modalHTML); yield this.generateQRCode(requestUrl, "reclaim-qr-code"); this.addEventListeners(); this.startAutoCloseTimer(); } catch (error) { logger6.info("Error showing QR code modal:", error); throw error; } }); } close() { if (this.autoCloseTimer) { clearTimeout(this.autoCloseTimer); this.autoCloseTimer = void 0; } if (this.countdownTimer) { clearInterval(this.countdownTimer); this.countdownTimer = void 0; } const modal = document.getElementById(this.modalId); if (modal) { modal.remove(); } if (this.options.onClose) { this.options.onClose(); } } getThemeStyles() { const isDark = this.options.darkTheme; return { modalBackground: isDark ? "rgba(0, 0, 0, 0.8)" : "rgba(0, 0, 0, 0.5)", cardBackground: isDark ? "#1f2937" : "white", titleColor: isDark ? "#f9fafb" : "#1f2937", textColor: isDark ? "#d1d5db" : "#6b7280", qrBackground: isDark ? "#374151" : "#f9fafb", tipBackground: isDark ? "#1e40af" : "#f0f9ff", tipBorder: isDark ? "#1e40af" : "#e0f2fe", tipTextColor: isDark ? "#dbeafe" : "#0369a1", buttonBackground: isDark ? "#374151" : "#f3f4f6", buttonColor: isDark ? "#f9fafb" : "#374151", buttonHoverBackground: isDark ? "#4b5563" : "#e5e7eb", countdownColor: isDark ? "#6b7280" : "#9ca3af", progressBackground: isDark ? "#4b5563" : "#e5e7eb", progressGradient: isDark ? "linear-gradient(90deg, #3b82f6 0%, #2563eb 50%, #1d4ed8 100%)" : "linear-gradient(90deg, #2563eb 0%, #1d4ed8 50%, #1e40af 100%)", linkColor: isDark ? "#60a5fa" : "#2563eb", extensionButtonBackground: isDark ? "#1e40af" : "#2563eb", extensionButtonHover: isDark ? "#1d4ed8" : "#1d4ed8" }; } createModalHTML() { const styles = this.getThemeStyles(); return ` <div id="${this.modalId}" style=" position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: ${styles.modalBackground}; display: flex; justify-content: center; align-items: center; z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; "> <div style=" background: ${styles.cardBackground}; border-radius: 12px; padding: 32px; max-width: 400px; width: 90%; text-align: center; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); position: relative; "> <button id="reclaim-close-modal" style=" position: absolute; top: 16px; right: 16px; background: none; border: none; cursor: pointer; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; width: 32px; height: 32px; " onmouseover="this.style.backgroundColor='${styles.buttonHoverBackground}'" onmouseout="this.style.backgroundColor='transparent'" title="Close modal"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 4L4 12M4 4L12 12" stroke="${styles.buttonColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <h2 style=" margin: 0 0 16px 0; font-size: 24px; font-weight: 600; color: ${styles.titleColor}; ">${this.options.title}</h2> <p style=" margin: 0 0 24px 0; color: ${styles.textColor}; font-size: 14px; line-height: 1.5; ">${this.options.description}</p> <div id="reclaim-qr-code" style=" margin: 0 auto 24px auto; background: ${styles.qrBackground}; border-radius: 8px; display: inline-block; "></div> ${this.options.showExtensionInstallButton ? ` <div style=" margin-bottom: 24px; padding: 16px; background: ${styles.tipBackground}; border: 1px solid ${styles.tipBorder}; border-radius: 8px; "> <p style=" margin: 0 0 12px 0; font-size: 14px; color: ${styles.tipTextColor}; font-weight: 500; ">\u{1F4A1} For a better experience</p> <p style=" margin: 0 0 12px 0; font-size: 13px; color: ${styles.tipTextColor}; line-height: 1.4; ">Install our browser extension for seamless verification without QR codes</p> <a href="${this.options.extensionUrl}" target="_blank" style=" display: inline-block; background: ${styles.extensionButtonBackground}; color: white; text-decoration: none; padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 500; transition: background-color 0.2s; " onmouseover="this.style.backgroundColor='${styles.extensionButtonHover}'" onmouseout="this.style.backgroundColor='${styles.extensionButtonBackground}'"> Install Extension </a> </div>` : ""} <div style="margin-top: 16px;"> <div id="reclaim-countdown" style=" font-size: 12px; color: ${styles.countdownColor}; font-weight: 400; margin-bottom: 8px; ">Auto-close in 1:00</div> <div style=" width: 100%; height: 4px; background-color: ${styles.progressBackground}; border-radius: 2px; overflow: hidden; "> <div id="reclaim-progress-bar" style=" width: 100%; height: 100%; background: ${styles.progressGradient}; border-radius: 2px; transition: width 1s linear; "></div> </div> </div> </div> </div> `; } generateQRCode(text, containerId) { return __async(this, null, function* () { try { const qrCodeUrl = `${constants.QR_CODE_API_URL}?size=200x200&data=${encodeURIComponent(text)}`; const container = document.getElementById(containerId); const styles = this.getThemeStyles(); if (container) { container.innerHTML = ` <img src="${qrCodeUrl}" alt="QR Code for Reclaim verification" style="width: 200px; height: 200px; border-radius: 4px;" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';"> <div style="display: none; padding: 20px; color: ${styles.textColor}; font-size: 14px;"> QR code could not be loaded.<br> <a href="${text}" target="_blank" style="color: ${styles.linkColor}; text-decoration: underline;"> Click here to open verification link </a> </div> `; } } catch (error) { logger6.info("Error generating QR code:", error); const container = document.getElementById(containerId); const styles = this.getThemeStyles(); if (container) { container.innerHTML = ` <div style="padding: 20px; color: ${styles.textColor}; font-size: 14px;"> <a href="${text}" target="_blank" style="color: ${styles.linkColor}; text-decoration: underline;"> Click here to open verification link </a> </div> `; } } }); } addEventListeners() { const closeButton = document.getElementById("reclaim-close-modal"); const modal = document.getElementById(this.modalId); const closeModal = () => { this.close(); }; if (closeButton) { closeButton.addEventListener("click", closeModal); } if (modal) { modal.addEventListener("click", (e) => { if (e.target === modal) { closeModal(); } }); } const handleEscape = (e) =>