@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
JavaScript
"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) =>