UNPKG

@ledgerhq/evm-tools

Version:

EVM tooling used for coin integrations & app bindings

241 lines 9.87 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEIP712FieldsDisplayedOnNano = exports.getValueFromPath = exports.getFiltersForMessage = exports.getSchemaHashForMessage = exports.sortObjectAlphabetically = exports.isEIP712Message = void 0; const axios_1 = __importDefault(require("axios")); const sha224_1 = __importDefault(require("crypto-js/sha224")); const live_env_1 = require("@ledgerhq/live-env"); const constants_1 = require("@ethersproject/constants"); const hash_1 = require("@ethersproject/hash"); // As defined in [spec](https://eips.ethereum.org/EIPS/eip-712), the properties below are all required. function isEIP712Message(message) { return (!!message && typeof message === "object" && "types" in message && "primaryType" in message && "domain" in message && "message" in message); } exports.isEIP712Message = isEIP712Message; const sortObjectAlphabetically = (obj) => { const keys = Object.keys(obj).sort(); return keys.reduce((acc, curr) => { const value = (() => { if (Array.isArray(obj[curr])) { return obj[curr].map(field => (0, exports.sortObjectAlphabetically)(field)); } return obj[curr]; })(); acc[curr] = value; return acc; }, {}); }; exports.sortObjectAlphabetically = sortObjectAlphabetically; const getSchemaHashForMessage = (message) => { const { types } = message; const sortedTypes = (0, exports.sortObjectAlphabetically)(types); return (0, sha224_1.default)(JSON.stringify(sortedTypes).replace(" ", "")).toString(); }; exports.getSchemaHashForMessage = getSchemaHashForMessage; /** * Tries to find the proper filters for a given EIP712 message * in the CAL * * @param {EIP712Message} message * @param {boolean} shouldUseV1Filters * @param {string | null} calServiceURL * @param {Record<string, any> | null | undefined} staticEIP712SignaturesV1 - Static EIP712 signatures v1 fallback * @param {Record<string, any> | null | undefined} staticEIP712SignaturesV2 - Static EIP712 signatures v2 fallback * @returns {MessageFilters | undefined} */ const getFiltersForMessage = async (message, shouldUseV1Filters, calServiceURL, staticEIP712SignaturesV1, staticEIP712SignaturesV2) => { const schemaHash = (0, exports.getSchemaHashForMessage)(message); const verifyingContract = message.domain?.verifyingContract?.toLowerCase() || constants_1.AddressZero; try { if (calServiceURL) { const { data } = await axios_1.default.get(`${calServiceURL}/v1/dapps`, { params: { output: "eip712_signatures", eip712_signatures_version: shouldUseV1Filters ? "v1" : "v2", chain_id: message.domain?.chainId, contracts: verifyingContract, }, }); // Rather than relying on array indices, find the right object wherever it may be, if it exists const targetObject = data.find(item => item?.eip712_signatures?.[verifyingContract]?.[schemaHash]); const filters = targetObject?.eip712_signatures?.[verifyingContract]?.[schemaHash]; if (!filters) { // Fallback to catch throw new Error("Fallback to static file"); } return filters; } // Fall through to static fallback throw new Error("No CAL service URL"); } catch (e) { // Static fallback from injected signatures (for external library users) const messageId = `${message.domain?.chainId ?? 0}:${verifyingContract}:${schemaHash}`; if (shouldUseV1Filters && staticEIP712SignaturesV1) { return staticEIP712SignaturesV1[messageId]; } if (!shouldUseV1Filters && staticEIP712SignaturesV2) { return staticEIP712SignaturesV2[messageId]; } return undefined; } }; exports.getFiltersForMessage = getFiltersForMessage; /** * Get the value at a specific path of an object and return it as a string or as an array of string * Used recursively by getValueFromPath * * @see getValueFromPath */ const getValue = (path, value) => { if (typeof value === "object") { if (Array.isArray(value)) { return value.map(v => getValue(path, v)).flat(); } /* istanbul ignore if : unecessary test of a throw */ if (!(path in value)) { throw new Error(`Could not find key ${path} in ${JSON.stringify(value)} `); } const result = value[path]; return typeof result === "object" ? result : result.toString(); } return value.toString(); }; /** * Using a path as a string, returns the value(s) of a json key without worrying about depth or arrays * (e.g: 'to.wallets.[]' => ["0x123", "0x456"]) */ const getValueFromPath = (path, eip721Message) => { const splittedPath = path.split("."); const { message } = eip721Message; let value = message; for (let i = 0; i <= splittedPath.length - 1; i++) { const subPath = splittedPath[i]; const isLastElement = i >= splittedPath.length - 1; if (subPath === "[]" && !isLastElement) continue; value = getValue(subPath, value); } /* istanbul ignore if : unecessary test of a throw */ if (value === message) { throw new Error("getValueFromPath returned the whole original message"); } return value; }; exports.getValueFromPath = getValueFromPath; function formatDate(timestamp) { const date = new Date(Number(timestamp) * 1000); if (isNaN(date.getTime())) { return timestamp; } const formatter = new Intl.DateTimeFormat("en-GB", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZone: "UTC", hour12: false, }); const parts = formatter.formatToParts(date); const p = (type) => parts.find(p => p.type === type)?.value || "00"; return `${p("year")}-${p("month")}-${p("day")} ${p("hour")}:${p("minute")}:${p("second")} UTC`; } /** * Gets the fields visible on the nano for a specific EIP712 message */ const getEIP712FieldsDisplayedOnNano = async (messageData, calServiceURL = (0, live_env_1.getEnv)("CAL_SERVICE_URL"), staticEIP712SignaturesV1, staticEIP712SignaturesV2) => { if (!isEIP712Message(messageData)) { return null; } const { EIP712Domain, ...otherTypes } = messageData.types; const displayedInfos = []; const filters = await (0, exports.getFiltersForMessage)(messageData, false, calServiceURL, staticEIP712SignaturesV1, staticEIP712SignaturesV2); if (!filters) { const { types } = messageData; const domainFields = types["EIP712Domain"].map(({ name }) => name); if (domainFields.includes("name") && messageData.domain.name) { displayedInfos.push({ label: "name", value: messageData.domain.name, }); } if (domainFields.includes("version") && messageData.domain.version) { displayedInfos.push({ label: "version", value: messageData.domain.version, }); } if (domainFields.includes("chainId") && messageData.domain.chainId) { displayedInfos.push({ label: "chainId", value: messageData.domain.chainId.toString(), }); } if (domainFields.includes("verifyingContract") && messageData.domain.verifyingContract) { displayedInfos.push({ label: "verifyingContract", value: messageData.domain.verifyingContract.toString(), }); } if (domainFields.includes("salt") && messageData.domain.salt) { displayedInfos.push({ label: "salt", value: messageData.domain.salt.toString(), }); } displayedInfos.push({ label: "Message hash", value: hash_1._TypedDataEncoder.hashStruct(messageData.primaryType, otherTypes, messageData.message), }); return displayedInfos; } const { contractName, fields } = filters; if (contractName && contractName.label) { displayedInfos.push({ label: "Contract", value: contractName.label, }); } if (messageData.primaryType === "PermitSingle") { for (const field of fields) { if (field.path.includes("token")) { displayedInfos.push({ label: "Token", value: (0, exports.getValueFromPath)(field.path, messageData), }); } else if (field.path.includes("amount")) { displayedInfos.push({ label: "Amount", value: (0, exports.getValueFromPath)(field.path, messageData), }); } else if (field.path.includes("expiration")) { displayedInfos.push({ label: "Approval expires", value: formatDate((0, exports.getValueFromPath)(field.path, messageData)), }); } } } else { for (const field of fields) { displayedInfos.push({ label: field.label, value: (0, exports.getValueFromPath)(field.path, messageData), }); } } return displayedInfos; }; exports.getEIP712FieldsDisplayedOnNano = getEIP712FieldsDisplayedOnNano; //# sourceMappingURL=index.js.map