UNPKG

@settlemint/sdk-eas

Version:

Ethereum Attestation Service (EAS) integration for SettleMint SDK

740 lines (734 loc) • 25.6 kB
/* SettleMint EAS SDK - Portal Optimized */ //#region rolldown:runtime var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion const __settlemint_sdk_portal = __toESM(require("@settlemint/sdk-portal")); const __settlemint_sdk_utils_logging = __toESM(require("@settlemint/sdk-utils/logging")); const __settlemint_sdk_utils_validation = __toESM(require("@settlemint/sdk-utils/validation")); const viem = __toESM(require("viem")); const zod = __toESM(require("zod")); //#region src/portal/operations.ts const GraphQLOperations = { mutations: { deploySchemaRegistry: (graphql) => graphql(` mutation DeployContractEASSchemaRegistry( $from: String! $constructorArguments: DeployContractEASSchemaRegistryInput! $gasLimit: String! ) { DeployContractEASSchemaRegistry(from: $from, constructorArguments: $constructorArguments, gasLimit: $gasLimit) { transactionHash } }`), deployEAS: (graphql) => graphql(` mutation DeployContractEAS($from: String!, $constructorArguments: DeployContractEASInput!, $gasLimit: String!) { DeployContractEAS(from: $from, constructorArguments: $constructorArguments, gasLimit: $gasLimit) { transactionHash } }`), registerSchema: (graphql) => graphql(` mutation EASSchemaRegistryRegister( $address: String! $from: String! $input: EASSchemaRegistryRegisterInput! $gasLimit: String! ) { EASSchemaRegistryRegister(address: $address, from: $from, input: $input, gasLimit: $gasLimit) { transactionHash } }`), attest: (graphql) => graphql(` mutation EASAttest($address: String!, $from: String!, $input: EASAttestInput!, $gasLimit: String!) { EASAttest(address: $address, from: $from, input: $input, gasLimit: $gasLimit) { transactionHash } }`), multiAttest: (graphql) => graphql(` mutation EASMultiAttest($address: String!, $from: String!, $input: EASMultiAttestInput!, $gasLimit: String!) { EASMultiAttest(address: $address, from: $from, input: $input, gasLimit: $gasLimit) { transactionHash } }`), revoke: (graphql) => graphql(` mutation EASRevoke($address: String!, $from: String!, $input: EASRevokeInput!, $gasLimit: String!) { EASRevoke(address: $address, from: $from, input: $input, gasLimit: $gasLimit) { transactionHash } }`) }, queries: { getSchema: (graphql) => graphql(` query EASSchemaRegistryGetSchema($address: String!, $uid: String!) { EASSchemaRegistry(address: $address) { getSchema(uid: $uid) { uid resolver revocable schema } } }`), getAttestation: (graphql) => graphql(` query EASGetAttestation($address: String!, $uid: String!) { EAS(address: $address) { getAttestation(uid: $uid) { uid schema attester recipient time expirationTime revocable refUID data revocationTime } } }`), isAttestationValid: (graphql) => graphql(` query EASIsAttestationValid($address: String!, $uid: String!) { EAS(address: $address) { isAttestationValid(uid: $uid) } }`), getTimestamp: (graphql) => graphql(` query EASGetTimestamp($address: String!, $data: String!) { EAS(address: $address) { getTimestamp(data: $data) } }`) } }; //#endregion //#region src/schema.ts /** * Common address constants */ const ZERO_ADDRESS = viem.zeroAddress; const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; /** * Supported field types for EAS schema fields. * Maps to the Solidity types that can be used in EAS schemas. */ const EAS_FIELD_TYPES = { string: "string", address: "address", bool: "bool", bytes: "bytes", bytes32: "bytes32", uint256: "uint256", int256: "int256", uint8: "uint8", int8: "int8" }; //#endregion //#region src/utils/validation.ts const ethAddressSchema = zod.z.custom((val) => typeof val === "string" && (0, viem.isAddress)(val), "Invalid Ethereum address"); /** * Zod schema for EASClientOptions. */ const EASClientOptionsSchema = zod.z.object({ instance: __settlemint_sdk_utils_validation.UrlSchema, accessToken: __settlemint_sdk_utils_validation.ApplicationAccessTokenSchema.optional(), easContractAddress: ethAddressSchema.optional(), schemaRegistryContractAddress: ethAddressSchema.optional(), debug: zod.z.boolean().optional() }); //#endregion //#region src/eas.ts const LOGGER = (0, __settlemint_sdk_utils_logging.createLogger)(); const DEFAULT_GAS_LIMIT = "0x3d0900"; /** * Main EAS client class for interacting with Ethereum Attestation Service via Portal * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * // Deploy EAS contracts * const deployment = await easClient.deploy("0x1234...deployer-address"); * console.log("EAS deployed at:", deployment.easAddress); * ``` */ var EASClient = class { options; portalClient; portalGraphql; deployedAddresses; /** * Create a new EAS client instance * * @param options - Configuration options for the EAS client */ constructor(options) { this.options = (0, __settlemint_sdk_utils_validation.validate)(EASClientOptionsSchema, options); const { client: portalClient, graphql: portalGraphql } = (0, __settlemint_sdk_portal.createPortalClient)({ instance: this.options.instance, accessToken: this.options.accessToken }, { fetch: (0, __settlemint_sdk_utils_logging.requestLogger)(LOGGER, "portal", fetch) }); this.portalClient = portalClient; this.portalGraphql = portalGraphql; } /** * Deploy EAS contracts via Portal * * @param deployerAddress - The address that will deploy the contracts * @param forwarderAddress - Optional trusted forwarder address (defaults to zero address) * @param gasLimit - Optional gas limit for deployment transactions (defaults to "0x7a1200") * @returns Promise resolving to deployment result with contract addresses and transaction hashes * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * const deployment = await easClient.deploy( * "0x1234567890123456789012345678901234567890", // deployer address * "0x0000000000000000000000000000000000000000", // forwarder (optional) * "0x7a1200" // gas limit (optional) * ); * * console.log("Schema Registry:", deployment.schemaRegistryAddress); * console.log("EAS Contract:", deployment.easAddress); * ``` */ async deploy(deployerAddress, forwarderAddress, gasLimit) { const defaultForwarder = forwarderAddress || ZERO_ADDRESS; const defaultGasLimit = gasLimit || "0x7a1200"; try { const schemaRegistryResponse = await this.portalClient.request(GraphQLOperations.mutations.deploySchemaRegistry(this.portalGraphql), { from: deployerAddress, constructorArguments: { forwarder: defaultForwarder }, gasLimit: defaultGasLimit }); if (!schemaRegistryResponse.DeployContractEASSchemaRegistry?.transactionHash) { throw new Error("Schema Registry deployment failed - no transaction hash returned"); } const schemaRegistryTxHash = schemaRegistryResponse.DeployContractEASSchemaRegistry.transactionHash; const schemaRegistryTransaction = await (0, __settlemint_sdk_portal.waitForTransactionReceipt)(schemaRegistryTxHash, { portalGraphqlEndpoint: this.options.instance, accessToken: this.options.accessToken, timeout: 6e4 }); if (!schemaRegistryTransaction?.receipt?.contractAddress) { throw new Error("Schema Registry deployment failed - could not get contract address from transaction receipt."); } const schemaRegistryAddress = schemaRegistryTransaction.receipt.contractAddress; const easResponse = await this.portalClient.request(GraphQLOperations.mutations.deployEAS(this.portalGraphql), { from: deployerAddress, constructorArguments: { registry: schemaRegistryAddress, forwarder: defaultForwarder }, gasLimit: defaultGasLimit }); if (!easResponse.DeployContractEAS?.transactionHash) { throw new Error("EAS deployment failed - no transaction hash returned"); } const easTxHash = easResponse.DeployContractEAS.transactionHash; const easTransaction = await (0, __settlemint_sdk_portal.waitForTransactionReceipt)(easTxHash, { portalGraphqlEndpoint: this.options.instance, accessToken: this.options.accessToken, timeout: 6e4 }); if (!easTransaction?.receipt?.contractAddress) { throw new Error("EAS deployment failed - could not get contract address from transaction receipt."); } const easAddress = easTransaction.receipt.contractAddress; this.deployedAddresses = { easAddress, schemaRegistryAddress, easTransactionHash: easTxHash, schemaRegistryTransactionHash: schemaRegistryTxHash }; return this.deployedAddresses; } catch (err) { const error = err; throw new Error(`Failed to deploy EAS contracts: ${error.message}`); } } /** * Register a new schema in the EAS Schema Registry * * @param request - Schema registration request containing schema definition * @param fromAddress - Address that will register the schema * @param gasLimit - Optional gas limit for the transaction (defaults to "0x3d0900") * @returns Promise resolving to transaction result * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * const schemaResult = await easClient.registerSchema( * { * schema: "uint256 eventId, uint8 voteIndex", * resolver: "0x0000000000000000000000000000000000000000", * revocable: true * }, * "0x1234567890123456789012345678901234567890" // from address * ); * * console.log("Schema registered:", schemaResult.hash); * ``` */ async registerSchema(request, fromAddress, gasLimit) { const schemaRegistryAddress = this.getSchemaRegistryAddress(); let schemaString = request.schema; if (request.fields && !schemaString) { schemaString = this.buildSchemaString(request.fields); } if (!schemaString) { throw new Error("Schema string is required. Provide either 'schema' or 'fields'."); } try { const response = await this.portalClient.request(GraphQLOperations.mutations.registerSchema(this.portalGraphql), { address: schemaRegistryAddress, from: fromAddress, input: { schema: schemaString, resolver: request.resolver, revocable: request.revocable }, gasLimit: gasLimit || DEFAULT_GAS_LIMIT }); const transactionHash = response.EASSchemaRegistryRegister?.transactionHash; if (!transactionHash) { throw new Error("No transaction hash returned from Portal"); } return { hash: transactionHash, success: true }; } catch (err) { const error = err; throw new Error(`Failed to register schema: ${error.message}`); } } /** * Create an attestation * * @param request - Attestation request containing schema and data * @param fromAddress - Address that will create the attestation * @param gasLimit - Optional gas limit for the transaction (defaults to "0x3d0900") * @returns Promise resolving to transaction result * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * const attestationResult = await easClient.attest( * { * schema: "0x1234567890123456789012345678901234567890123456789012345678901234", * data: { * recipient: "0x1234567890123456789012345678901234567890", * expirationTime: BigInt(0), // No expiration * revocable: true, * refUID: "0x0000000000000000000000000000000000000000000000000000000000000000", * data: "0x1234", // ABI-encoded data * value: BigInt(0) * } * }, * "0x1234567890123456789012345678901234567890" // from address * ); * * console.log("Attestation created:", attestationResult.hash); * ``` */ async attest(request, fromAddress, gasLimit) { const easAddress = this.getEASAddress(); try { const response = await this.portalClient.request(GraphQLOperations.mutations.attest(this.portalGraphql), { address: easAddress, from: fromAddress, input: { request: { schema: request.schema, data: { recipient: request.data.recipient, expirationTime: request.data.expirationTime.toString(), revocable: request.data.revocable, refUID: request.data.refUID, data: request.data.data, value: request.data.value?.toString() || "0" } } }, gasLimit: gasLimit || DEFAULT_GAS_LIMIT }); const transactionHash = response.EASAttest?.transactionHash; if (!transactionHash) { throw new Error("No transaction hash returned from Portal"); } return { hash: transactionHash, success: true }; } catch (err) { const error = err; throw new Error(`Failed to create attestation: ${error.message}`); } } /** * Create multiple attestations in a single transaction * * @param requests - Array of attestation requests * @param fromAddress - Address that will create the attestations * @param gasLimit - Optional gas limit for the transaction (defaults to "0x3d0900") * @returns Promise resolving to transaction result * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * const multiAttestResult = await easClient.multiAttest( * [ * { * schema: "0x1234567890123456789012345678901234567890123456789012345678901234", * data: { * recipient: "0x1234567890123456789012345678901234567890", * expirationTime: BigInt(0), * revocable: true, * refUID: "0x0000000000000000000000000000000000000000000000000000000000000000", * data: "0x1234", * value: BigInt(0) * } * }, * { * schema: "0x5678901234567890123456789012345678901234567890123456789012345678", * data: { * recipient: "0x5678901234567890123456789012345678901234", * expirationTime: BigInt(0), * revocable: false, * refUID: "0x0000000000000000000000000000000000000000000000000000000000000000", * data: "0x5678", * value: BigInt(0) * } * } * ], * "0x1234567890123456789012345678901234567890" // from address * ); * * console.log("Multiple attestations created:", multiAttestResult.hash); * ``` */ async multiAttest(requests, fromAddress, gasLimit) { if (requests.length === 0) { throw new Error("At least one attestation request is required"); } const easAddress = this.getEASAddress(); try { const response = await this.portalClient.request(GraphQLOperations.mutations.multiAttest(this.portalGraphql), { address: easAddress, from: fromAddress, input: { multiRequests: requests.map((req) => ({ schema: req.schema, data: [{ recipient: req.data.recipient, expirationTime: req.data.expirationTime.toString(), revocable: req.data.revocable, refUID: req.data.refUID, data: req.data.data, value: req.data.value?.toString() || "0" }] })) }, gasLimit: gasLimit || DEFAULT_GAS_LIMIT }); const transactionHash = response.EASMultiAttest?.transactionHash; if (!transactionHash) { throw new Error("No transaction hash returned from Portal"); } return { hash: transactionHash, success: true }; } catch (err) { const error = err; throw new Error(`Failed to create multiple attestations: ${error.message}`); } } /** * Revoke an existing attestation * * @param schemaUID - UID of the schema used for the attestation * @param attestationUID - UID of the attestation to revoke * @param fromAddress - Address that will revoke the attestation * @param value - Optional ETH value to send with the revocation * @param gasLimit - Optional gas limit for the transaction (defaults to "0x3d0900") * @returns Promise resolving to transaction result * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * const revokeResult = await easClient.revoke( * "0x1234567890123456789012345678901234567890123456789012345678901234", // schema UID * "0x5678901234567890123456789012345678901234567890123456789012345678", // attestation UID * "0x1234567890123456789012345678901234567890", // from address * BigInt(0) // value (optional) * ); * * console.log("Attestation revoked:", revokeResult.hash); * ``` */ async revoke(schemaUID, attestationUID, fromAddress, value, gasLimit) { try { const response = await this.portalClient.request(GraphQLOperations.mutations.revoke(this.portalGraphql), { address: this.getEASAddress(), from: fromAddress, input: { request: { schema: schemaUID, data: { uid: attestationUID, value: value?.toString() || "0" } } }, gasLimit: gasLimit || DEFAULT_GAS_LIMIT }); const transactionHash = response.EASRevoke?.transactionHash; if (!transactionHash) { throw new Error("No transaction hash returned from Portal"); } return { hash: transactionHash, success: true }; } catch (err) { const error = err; throw new Error(`Failed to revoke attestation: ${error.message}`); } } /** * Get a schema by UID */ async getSchema(uid) { const schemaRegistryAddress = this.getSchemaRegistryAddress(); try { const response = await this.portalClient.request(GraphQLOperations.queries.getSchema(this.portalGraphql), { address: schemaRegistryAddress, uid }); const schemaResult = response.EASSchemaRegistry?.getSchema; if (!schemaResult) { throw new Error(`Schema not found: ${uid}`); } return { uid: schemaResult.uid, resolver: schemaResult.resolver, revocable: Boolean(schemaResult.revocable), schema: schemaResult.schema || "" }; } catch (err) { const error = err; throw new Error(`Failed to get schema: ${error.message}`); } } /** * Get all schemas with pagination * * Note: This method requires The Graph subgraph or additional indexing infrastructure * as Portal's direct contract queries don't support listing all schemas. * Consider using getSchema() for individual schema lookups. */ async getSchemas(_options) { throw new Error("Schema listing not implemented yet. Portal's direct contract queries don't support listing all schemas. Use getSchema() for individual schema lookups or implement The Graph subgraph integration for bulk queries."); } /** * Get an attestation by UID */ async getAttestation(uid) { const easAddress = this.getEASAddress(); try { const response = await this.portalClient.request(GraphQLOperations.queries.getAttestation(this.portalGraphql), { address: easAddress, uid }); const attestationResult = response.EAS?.getAttestation; if (!attestationResult) { throw new Error(`Attestation not found: ${uid}`); } return { uid: attestationResult.uid, schema: attestationResult.schema, attester: attestationResult.attester, recipient: attestationResult.recipient, time: attestationResult.time ? BigInt(attestationResult.time) : BigInt(0), expirationTime: attestationResult.expirationTime ? BigInt(attestationResult.expirationTime) : BigInt(0), revocable: Boolean(attestationResult.revocable), refUID: attestationResult.refUID, data: attestationResult.data, value: BigInt(0) }; } catch (err) { const error = err; throw new Error(`Failed to get attestation: ${error.message}`); } } /** * Get attestations with pagination and filtering * * Note: This method requires The Graph subgraph or additional indexing infrastructure * as Portal's direct contract queries don't support listing all attestations. * Consider using getAttestation() for individual attestation lookups. */ async getAttestations(_options) { throw new Error("Attestation listing not implemented yet. Portal's direct contract queries don't support listing all attestations. Use getAttestation() for individual attestation lookups or implement The Graph subgraph integration for bulk queries."); } /** * Check if an attestation is valid */ async isValidAttestation(uid) { const easAddress = this.getEASAddress(); try { const response = await this.portalClient.request(GraphQLOperations.queries.isAttestationValid(this.portalGraphql), { address: easAddress, uid }); return response.EAS?.isAttestationValid ?? false; } catch (err) { const error = err; throw new Error(`Failed to check attestation validity: ${error.message}`); } } /** * Get the timestamp for specific data * * @param data - The data to get timestamp for * @returns The timestamp when the data was timestamped */ async getTimestamp(data) { const easAddress = this.getEASAddress(); try { const response = await this.portalClient.request(GraphQLOperations.queries.getTimestamp(this.portalGraphql), { address: easAddress, data }); const timestampResult = response.EAS?.getTimestamp; if (timestampResult === undefined || timestampResult === null) { throw new Error(`No timestamp found for data: ${data}`); } return BigInt(timestampResult); } catch (err) { const error = err; throw new Error(`Failed to get timestamp: ${error.message}`); } } /** * Get client configuration */ getOptions() { return { ...this.options }; } /** * Get the Portal client instance for advanced operations */ getPortalClient() { return this.portalClient; } /** * Get current contract addresses */ getContractAddresses() { return { easAddress: this.options.easContractAddress || this.deployedAddresses?.easAddress, schemaRegistryAddress: this.options.schemaRegistryContractAddress || this.deployedAddresses?.schemaRegistryAddress }; } getEASAddress() { if (this.options.easContractAddress) { return this.options.easContractAddress; } if (this.deployedAddresses?.easAddress) { return this.deployedAddresses.easAddress; } throw new Error("EAS contract address not available. Please provide it in options or deploy contracts first."); } getSchemaRegistryAddress() { if (this.options.schemaRegistryContractAddress) { return this.options.schemaRegistryContractAddress; } if (this.deployedAddresses?.schemaRegistryAddress) { return this.deployedAddresses.schemaRegistryAddress; } throw new Error("Schema Registry contract address not available. Please provide it in options or deploy contracts first."); } buildSchemaString(fields) { return fields.map((field) => `${field.type} ${field.name}`).join(", "); } }; /** * Create an EAS client instance * * @param options - Configuration options for the EAS client * @returns EAS client instance * * @example * ```typescript * import { createEASClient } from "@settlemint/sdk-eas"; * * const easClient = createEASClient({ * instance: "https://your-portal-instance.settlemint.com", * accessToken: "your-access-token" * }); * * // Use the client * const deployment = await easClient.deploy("0x1234...deployer-address"); * ``` */ function createEASClient(options) { return new EASClient(options); } //#endregion exports.EASClient = EASClient; exports.EASClientOptionsSchema = EASClientOptionsSchema; exports.EAS_FIELD_TYPES = EAS_FIELD_TYPES; exports.GraphQLOperations = GraphQLOperations; exports.ZERO_ADDRESS = ZERO_ADDRESS; exports.ZERO_BYTES32 = ZERO_BYTES32; exports.createEASClient = createEASClient; //# sourceMappingURL=eas.cjs.map