@ledgerhq/evm-tools
Version:
EVM tooling used for coin integrations & app bindings
241 lines • 9.87 kB
JavaScript
;
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