@terra-money/ledger-terra-js
Version:
API for Terra App (Ledger Nano S/X)
216 lines • 9 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LedgerKey = exports.LedgerError = void 0;
const terra_js_1 = require("@terra-money/terra.js");
const terra_js_2 = require("@terra-money/terra.js");
const app_1 = __importDefault(require("./app"));
const secp256k1_1 = require("secp256k1");
const semver_1 = __importDefault(require("semver"));
const LUNA_COIN_TYPE = 330;
const INTERACTION_TIMEOUT = 120;
const REQUIRED_APP_VERSION = "1.0.0";
class LedgerError extends Error {
constructor(message) {
super(message);
this.name = "LedgerError";
}
}
exports.LedgerError = LedgerError;
class LedgerKey extends terra_js_1.Key {
constructor(transport) {
super();
this.transport = transport;
this.path = [44, LUNA_COIN_TYPE, 0, 0, 0];
this.app = new app_1.default(this.transport);
}
get accAddress() {
if (!this.publicKey) {
throw new Error("Ledger is unintialized. Initialize it first.");
}
return this.publicKey.address();
}
static create(transport, index) {
return __awaiter(this, void 0, void 0, function* () {
if (!transport) {
transport = yield createTransport();
}
const key = new LedgerKey(transport);
if (index != undefined) {
key.path[4] = index;
}
if (transport && typeof transport.on === "function") {
transport.on("disconnect", () => {
key.transport = null;
});
}
yield key.initialize().catch(handleConnectError);
return key;
});
}
initialize() {
return __awaiter(this, void 0, void 0, function* () {
const res = yield this.app.initialize();
const { app_name: appName } = this.app.getInfo();
if (appName !== "Terra") {
throw new LedgerError("Open the Terra app in the Ledger");
}
const { major, minor, patch } = this.app.getVersion();
const version = `${major}.${minor}.${patch}`;
if (appName === "Terra" && semver_1.default.lt(version, REQUIRED_APP_VERSION)) {
throw new LedgerError("Outdated version: Update Ledger Terra App to the latest version");
}
checkLedgerErrors(res);
yield this.loadAccountDetails();
});
}
loadAccountDetails() {
return __awaiter(this, void 0, void 0, function* () {
const res = yield this.app.getAddressAndPubKey(this.path, "terra");
checkLedgerErrors(res);
this._accAddress = res.bech32_address;
this.publicKey = new terra_js_2.SimplePublicKey(Buffer.from(res.compressed_pk.data).toString("base64"));
return this;
});
}
sign(message) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.publicKey) {
this.loadAccountDetails();
}
const res = yield this.app.sign(this.path, message);
checkLedgerErrors(res);
return Buffer.from((0, secp256k1_1.signatureImport)(Buffer.from(res.signature)));
});
}
createSignature(_tx) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error("direct sign mode is not supported");
});
}
getAppAddressAndPubKey() {
return __awaiter(this, void 0, void 0, function* () {
return this.app.getAddressAndPubKey(this.path, 'terra');
});
}
getAppInfo() {
return this.app.getInfo();
}
getAppDeviceInfo() {
return __awaiter(this, void 0, void 0, function* () {
return this.app.getDeviceInfo();
});
}
getAppPublicKey() {
return __awaiter(this, void 0, void 0, function* () {
return this.app.getPublicKey(this.path);
});
}
getAppVersion() {
return this.app.getVersion();
}
showAddressAndPubKey() {
return __awaiter(this, void 0, void 0, function* () {
return this.app.showAddressAndPubKey(this.path, 'terra');
});
}
}
exports.LedgerKey = LedgerKey;
const handleConnectError = (err) => {
const message = err.message.trim();
if (message.startsWith("The device is already open")) {
return;
}
if (err.name === "TransportOpenUserCancelled") {
throw new LedgerError("Couldn't find the Ledger. Check the Ledger is plugged in and unlocked.");
}
if (message.startsWith("No WebUSB interface found for the Ledger device")) {
throw new LedgerError(`Couldn't connect to a Ledger device. Use Ledger Live to upgrade the Ledger firmware to version ${REQUIRED_APP_VERSION} or later.`);
}
if (message.startsWith("Unable to claim interface")) {
throw new LedgerError("Couldn't access Ledger device. Is it being used in another tab?");
}
if (message.startsWith("Transport not defined")) {
throw new LedgerError("Couldn't access Ledger device. Is it being used in another tab?");
}
if (message.startsWith("Not supported")) {
throw new LedgerError("This browser doesn't support WebUSB yet. Update it to the latest version.");
}
if (message.startsWith("No device selected")) {
throw new LedgerError("Couldn't find the Ledger. Check the Ledger is plugged in and unlocked.");
}
throw err;
};
const checkLedgerErrors = (response) => {
if (!response) {
return;
}
const { error_message, device_locked } = response;
if (device_locked) {
throw new LedgerError("Ledger's screensaver mode is on");
}
if (error_message.startsWith("TransportRaceCondition")) {
throw new LedgerError("Finish previous action in Ledger");
}
else if (error_message.startsWith("DisconnectedDeviceDuringOperation")) {
throw new LedgerError("Open the Terra app in the Ledger");
}
switch (error_message) {
case "U2F: Timeout":
throw new LedgerError("Couldn't find a connected and unlocked Ledger device");
case "App does not seem to be open":
throw new LedgerError("Open the Terra app in the Ledger");
case "Command not allowed":
throw new LedgerError("Transaction rejected");
case "Transaction rejected":
throw new LedgerError("User rejected the transaction");
case "Unknown Status Code: 26628":
throw new LedgerError("Ledger's screensaver mode is on");
case "Instruction not supported":
throw new LedgerError("Check the Ledger is running latest version of Terra");
case "No errors":
break;
default:
throw new LedgerError(error_message);
}
};
const isWindows = (platform) => platform.indexOf("Win") > -1;
const checkBrowser = (userAgent) => {
const ua = userAgent.toLowerCase();
const isChrome = /chrome|crios/.test(ua) && !/edge|opr\//.test(ua);
const isBrave = isChrome && !window.google;
if (!isChrome && !isBrave) {
throw new LedgerError("This browser doesn't support Ledger devices");
}
return isChrome ? "chrome" : "brave";
};
function createTransport() {
return __awaiter(this, void 0, void 0, function* () {
let transport;
checkBrowser(navigator.userAgent);
if (isWindows(navigator.platform)) {
if (!navigator.hid) {
throw new LedgerError("This browser doesn't have HID enabled. Enable this feature by visiting: chrome://flags/#enable-experimental-web-platform-features");
}
const TransportWebHid = require("@ledgerhq/hw-transport-webhid").default;
transport = yield TransportWebHid.create(INTERACTION_TIMEOUT * 1000).catch(handleConnectError);
}
else {
const TransportWebUsb = require("@ledgerhq/hw-transport-webusb").default;
transport = yield TransportWebUsb.create(INTERACTION_TIMEOUT * 1000).catch(handleConnectError);
}
return transport;
});
}
//# sourceMappingURL=key.js.map