diff --git a/packages/yoroi-ergo-connector/example-cardano/index.html b/packages/yoroi-ergo-connector/example-cardano/index.html index e33d847135..ed10715b75 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.html +++ b/packages/yoroi-ergo-connector/example-cardano/index.html @@ -11,8 +11,18 @@

Cardano dApp Example

-
- +
+
+
+
+  Request identification

+ +
+
+ Connected to: ABCD-1234 + +
+
@@ -38,9 +48,9 @@

Cardano dApp Example

-
-
-
+
diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index 1fae492da9..4bed801b4c 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -1,6 +1,14 @@ import * as CardanoWasm from "@emurgo/cardano-serialization-lib-browser" -import { getTtl} from './utils' +import { textPartFromWalletChecksumImagePart } from "@emurgo/cip4-js" +import { createIcon } from "@download/blockies" +import { getTtl } from './utils' + +const cardanoAccessBtnRow = document.querySelector('#request-button-row') +const cardanoAuthCheck = document.querySelector('#check-identification') const cardanoAccessBtn = document.querySelector('#request-access') +const connectionStatus = document.querySelector('#connection-status') +const walletPlateSpan = document.querySelector('#wallet-plate') +const walletIconSpan = document.querySelector('#wallet-icon') const getUnUsedAddresses = document.querySelector('#get-unused-addresses') const getUsedAddresses = document.querySelector('#get-used-addresses') const getChangeAddress = document.querySelector('#get-change-address') @@ -18,15 +26,97 @@ let utxos let changeAddress let transactionHex +const mkcolor = (primary, secondary, spots) => ({ primary, secondary, spots }); +const COLORS = [ + mkcolor('#E1F2FF', '#17D1AA', '#A80B32'), + mkcolor('#E1F2FF', '#FA5380', '#0833B2'), + mkcolor('#E1F2FF', '#F06EF5', '#0804F7'), + mkcolor('#E1F2FF', '#EBB687', '#852D62'), + mkcolor('#E1F2FF', '#F59F9A', '#085F48'), +]; -cardanoAccessBtn.addEventListener('click', () => { - toggleSpinner('show') - cardano.yoroi.enable().then(function(api){ - toggleSpinner('hide') - alertSuccess( 'You have access now') - accessGranted = true - cardanoApi = api +function createBlockiesIcon(seed) { + const colorIdx = Buffer.from(seed, 'hex')[0] % COLORS.length; + const color = COLORS[colorIdx]; + return createIcon({ + seed, + size: 7, + scale: 5, + bgcolor: color.primary, + color: color.secondary, + spotcolor: color.spots, + }) +} + +toggleSpinner('show'); + +function onApiConnectied(api) { + toggleSpinner('hide'); + let walletDisplay = 'an anonymous Yoroi Wallet'; + + const auth = api.auth && api.auth(); + const authEnabled = auth && auth.isEnabled(); + + if (authEnabled) { + const walletId = auth.getWalletId(); + const pubkey = auth.getWalletPubkey(); + console.log('Auth acquired successfully: ', + JSON.stringify({ walletId, pubkey })); + const walletPlate = textPartFromWalletChecksumImagePart(walletId); + walletDisplay = `Yoroi Wallet ${walletPlate}`; + walletIconSpan.appendChild(createBlockiesIcon(walletId)); + } + + alertSuccess(`You have access to ${walletDisplay} now`); + walletPlateSpan.innerHTML = walletDisplay; + toggleConnectionUI('status'); + accessGranted = true; + cardanoApi = api; + + api.onDisconnect(() => { + alertWarrning(`Disconnected from ${walletDisplay}`); + toggleConnectionUI('button'); + walletPlateSpan.innerHTML = ''; + walletIconSpan.innerHTML = ''; + }); + + if (authEnabled) { + console.log('Testing auth signatures') + const messageJson = JSON.stringify({ + type: 'this is a random test message object', + rndValue: Math.random(), }); + const messageHex = Buffer.from(messageJson).toString('hex'); + console.log('Signing randomized message: ', JSON.stringify({ + messageJson, + messageHex, + })) + auth.signHexPayload(messageHex).then(sig => { + console.log('Signature received: ', sig); + console.log('Verifying signature against the message'); + auth.checkHexPayload(messageHex, sig).then(r => { + console.log('Signature matches message: ', r); + }, e => { + console.error('Sig check failed', e); + }); + }, err => { + console.error('Sig failed', err); + }); + } +} + +cardanoAccessBtn.addEventListener('click', () => { + toggleSpinner('show'); + const requestIdentification = cardanoAuthCheck.checked; + cardano.yoroi.enable({ requestIdentification }).then( + function(api){ + onApiConnectied(api); + }, + function (err) { + toggleSpinner('hide'); + alertError(`Error: ${err}`); + }, + ); }) getAccountBalance.addEventListener('click', () => { @@ -34,7 +124,7 @@ getAccountBalance.addEventListener('click', () => { alertError('Should request access first') } else { toggleSpinner('show') - cardanoApi.get_balance().then(function(balance) { + cardanoApi.getBalance().then(function(balance) { toggleSpinner('hide') alertSuccess(`Account Balance: ${balance}`) }); @@ -46,7 +136,7 @@ getUnUsedAddresses.addEventListener('click', () => { alertError('Should request access first') } else { toggleSpinner('show') - cardanoApi.get_unused_addresses().then(function(addresses) { + cardanoApi.getUnusedAddresses().then(function(addresses) { toggleSpinner('hide') if(addresses.length === 0){ alertWarrning('No unused addresses') @@ -63,7 +153,7 @@ getUsedAddresses.addEventListener('click', () => { alertError('Should request access first') } else { toggleSpinner('show') - cardanoApi.get_used_addresses().then(function(addresses) { + cardanoApi.getUsedAddresses().then(function(addresses) { toggleSpinner('hide') if(addresses.length === 0){ alertWarrning('No used addresses') @@ -80,7 +170,7 @@ getChangeAddress.addEventListener('click', () => { alertError('Should request access first') } else { toggleSpinner('show') - cardanoApi.get_change_address().then(function(address) { + cardanoApi.getChangeAddress().then(function(address) { toggleSpinner('hide') if(address.length === 0){ alertWarrning('No change addresses') @@ -99,7 +189,7 @@ getUtxos.addEventListener('click', () => { return } toggleSpinner('show') - cardanoApi.get_utxos().then(utxosResponse => { + cardanoApi.getUtxos().then(utxosResponse => { toggleSpinner('hide') if(utxosResponse.length === 0){ alertWarrning('NO UTXOS') @@ -122,7 +212,7 @@ submitTx.addEventListener('click', () => { } toggleSpinner('show') - cardanoApi.submit_tx(transactionHex).then(txId => { + cardanoApi.submitTx(transactionHex).then(txId => { toggleSpinner('hide') alertSuccess(`Transaction ${txId} submitted`); }).catch(error => { @@ -209,7 +299,7 @@ signTx.addEventListener('click', () => { const txBody = txBuilder.build() const txHex = Buffer.from(txBody.to_bytes()).toString('hex') - cardanoApi.sign_tx(txHex, true).then(witnessSetHex => { + cardanoApi.signTx(txHex, true).then(witnessSetHex => { toggleSpinner('hide') const witnessSet = CardanoWasm.TransactionWitnessSet.from_bytes( @@ -261,7 +351,7 @@ createTx.addEventListener('click', () => { ] } - cardanoApi.create_tx(txReq, true).then(txHex => { + cardanoApi.createTx(txReq, true).then(txHex => { toggleSpinner('hide') alertSuccess('Creating tx succeeds: ' + txHex) transactionHex = txHex @@ -272,15 +362,6 @@ createTx.addEventListener('click', () => { }) }) -if (typeof cardano === "undefined") { - alert("Cardano not found"); -} else { - console.log("Cardano found"); - window.addEventListener("cardano_wallet_disconnected", function(event) { - console.log("Wallet Disconnect") - }); -} - function alertError (text) { alertEl.className = 'alert alert-danger' alertEl.innerHTML = text @@ -304,3 +385,40 @@ function toggleSpinner(status){ spinner.className = 'd-none' } } + +function toggleConnectionUI(status) { + if (status === 'button') { + connectionStatus.classList.add('d-none'); + cardanoAccessBtnRow.classList.remove('d-none'); + } else { + cardanoAccessBtnRow.classList.add('d-none'); + connectionStatus.classList.remove('d-none'); + } +} + +const onload = window.onload; +window.onload = function() { + if (onload) { + onload(); + } + if (typeof window.cardano === "undefined") { + alertError("Cardano API not found"); + } else { + console.log("Cardano API detected, checking connection status"); + cardano.yoroi.enable({ requestIdentification: true, onlySilent: true }).then( + api => { + console.log('successful silent reconnection') + onApiConnectied(api); + }, + err => { + if (String(err).includes('onlySilent:fail')) { + console.log('no silent re-connection available'); + } else { + console.error('Silent reconnection failed for unknown reason!', err); + } + toggleSpinner('hide'); + toggleConnectionUI('button'); + } + ); + } +} diff --git a/packages/yoroi-ergo-connector/example-cardano/package-lock.json b/packages/yoroi-ergo-connector/example-cardano/package-lock.json index 6b5bff6373..fefaf2d80b 100644 --- a/packages/yoroi-ergo-connector/example-cardano/package-lock.json +++ b/packages/yoroi-ergo-connector/example-cardano/package-lock.json @@ -4,12 +4,27 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@download/blockies": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@download/blockies/-/blockies-1.0.3.tgz", + "integrity": "sha512-iGDh2M6pFuXg9kyW+U//963LKylSLFpLG5hZvUppCjhkiDwsYquQPyamxCQlLASYySS3gGKAki2eWG9qIHKCew==" + }, "@emurgo/cardano-serialization-lib-browser": { "version": "9.1.4", "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-browser/-/cardano-serialization-lib-browser-9.1.4.tgz", "integrity": "sha512-W+bdbPx2RTZilMYGi5Bo8LmMBgWlGfNggRqdVlMRgk7jQbMa61P88f4C0CO3GMdzOOTKH2K0yXg0hr/9cuh7tA==", "dev": true }, + "@emurgo/cip4-js": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@emurgo/cip4-js/-/cip4-js-1.0.6.tgz", + "integrity": "sha512-8fM3LeK1USLsECSHd+6Rj+RV306pQmZ35LtC7jHHM7V7dMfUE8B4r/rFSoYUjNU/58T/8+Hgt0QhjEYDWMviwQ==", + "requires": { + "blake2b": "2.1.3", + "buffer-crc32": "0.2.13", + "fnv-plus": "1.3.1" + } + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -529,6 +544,23 @@ "file-uri-to-path": "1.0.0" } }, + "blake2b": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.3.tgz", + "integrity": "sha512-pkDss4xFVbMb4270aCyGD3qLv92314Et+FsKzilCLxDz5DuZ2/1g3w4nmBbu6nKApPspnjG7JcwTjGZnduB1yg==", + "requires": { + "blake2b-wasm": "^1.1.0", + "nanoassert": "^1.0.0" + } + }, + "blake2b-wasm": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-1.1.7.tgz", + "integrity": "sha512-oFIHvXhlz/DUgF0kq5B1CqxIDjIJwh9iDeUUGQUcvgiGz7Wdw03McEO7CfLBy7QKGdsydcMCgO9jFNBAFCtFcA==", + "requires": { + "nanoassert": "^1.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -729,6 +761,11 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1931,6 +1968,11 @@ "readable-stream": "^2.3.6" } }, + "fnv-plus": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fnv-plus/-/fnv-plus-1.3.1.tgz", + "integrity": "sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw==" + }, "follow-redirects": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.0.tgz", @@ -2990,6 +3032,11 @@ "dev": true, "optional": true }, + "nanoassert": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", + "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", diff --git a/packages/yoroi-ergo-connector/example-cardano/package.json b/packages/yoroi-ergo-connector/example-cardano/package.json index 189b370570..a87b49b54a 100644 --- a/packages/yoroi-ergo-connector/example-cardano/package.json +++ b/packages/yoroi-ergo-connector/example-cardano/package.json @@ -18,6 +18,8 @@ "copy-webpack-plugin": "^5.0.0" }, "dependencies": { + "@emurgo/cip4-js": "1.0.6", + "@download/blockies": "1.0.3", "bignumber.js": "^9.0.1" } } diff --git a/packages/yoroi-ergo-connector/example-cardano/subpage.html b/packages/yoroi-ergo-connector/example-cardano/subpage.html new file mode 100644 index 0000000000..7d58463526 --- /dev/null +++ b/packages/yoroi-ergo-connector/example-cardano/subpage.html @@ -0,0 +1,61 @@ + + + + + + Cardano Test dApp + + +
+
+

Cardano dApp Example / Cardano

+
+
+
+
+
+
+  Request identification

+ +
+
+ Connected to: ABCD-1234 + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ Go back to main page +
+ +
+ + + + + diff --git a/packages/yoroi-ergo-connector/manifest/manifest.template.js b/packages/yoroi-ergo-connector/manifest/manifest.template.js index a711581a1f..9058c4e0fd 100644 --- a/packages/yoroi-ergo-connector/manifest/manifest.template.js +++ b/packages/yoroi-ergo-connector/manifest/manifest.template.js @@ -30,8 +30,5 @@ module.exports = ({ "default_title": displayName, default_icon: icons, }, - "content_security_policy": "default-src 'none'; script-src 'self';", - "permissions": [ - "activeTab", - ] + "content_security_policy": "default-src 'none'; script-src 'self';" }); diff --git a/packages/yoroi-ergo-connector/src/inject.js b/packages/yoroi-ergo-connector/src/inject.js index 35ca0d5e3c..c7fe6306a3 100644 --- a/packages/yoroi-ergo-connector/src/inject.js +++ b/packages/yoroi-ergo-connector/src/inject.js @@ -1,6 +1,6 @@ // sets up RPC communication with the connector + access check/request functions const WALLET_NAME = 'yoroi'; -const API_VERSION = '0.1.0'; +const API_VERSION = '0.2.0'; const initialInject = ` (() => { @@ -11,7 +11,18 @@ const initialInject = ` if (event.data.err !== undefined) { connectRequests.forEach(promise => promise.reject(event.data.err)); } else { - connectRequests.forEach(promise => promise.resolve(event.data.success)); + const isSuccess = event.data.success; + connectRequests.forEach(promise => { + if (promise.protocol === 'cardano') { + if (isSuccess) { + promise.resolve(event.data.auth); + } else { + promise.reject(new Error('user reject')); + } + } else { + promise.resolve(isSuccess); + } + }); } } }); @@ -39,7 +50,7 @@ const initialInject = ` window.addEventListener("message", function(event) { if (event.data.type == "connector_rpc_response" && event.data.protocol === "cardano") { - console.log("page received from connector: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin); + console.debug("page received from connector: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin); const rpcPromise = cardanoRpcResolver.get(event.data.uid); if (rpcPromise !== undefined) { const ret = event.data.return; @@ -51,73 +62,35 @@ const initialInject = ` } } }); - - class CardanoAPI { - constructor(){ - this.initTimestamp = Date.now() - } - - getInitTimestamp(){ - return this.initTimestamp - } - - get_balance(token_id = 'ADA') { - return this._cardano_rpc_call("get_balance", [token_id]); - } - - get_used_addresses(paginate = undefined) { - return this._cardano_rpc_call("get_used_addresses", [paginate]); - } - - get_unused_addresses() { - return this._cardano_rpc_call("get_unused_addresses", []); - } - - get_change_address() { - return this._cardano_rpc_call("get_change_address", []); - } - - get_utxos(amount = undefined, token_id = 'ADA', paginate = undefined) { - return this._cardano_rpc_call("get_utxos", [amount, token_id, paginate]); - } - - submit_tx(tx) { - return this._cardano_rpc_call('submit_tx', [tx]); - } - - sign_tx(tx, partialSign = false) { - return this._cardano_rpc_call('sign_tx/cardano', [{ tx, partialSign }]); - } - - create_tx(req) { - return this._cardano_rpc_call("create_tx/cardano", [req]); - } - - _cardano_rpc_call(func, params) { - return new Promise(function(resolve, reject) { - window.postMessage({ - type: "connector_rpc_request", - protocol: "cardano", - uid: cardanoRpcUid, - function: func, - params: params - }, location.origin); - console.log("cardanoRpcUid = " + cardanoRpcUid); - cardanoRpcResolver.set(cardanoRpcUid, { resolve: resolve, reject: reject }); - cardanoRpcUid += 1; - }); - } + + function cardano_rpc_call(func, params) { + return new Promise(function(resolve, reject) { + window.postMessage({ + type: "connector_rpc_request", + protocol: "cardano", + uid: cardanoRpcUid, + function: func, + params: params + }, location.origin); + console.debug("cardanoRpcUid = " + cardanoRpcUid); + cardanoRpcResolver.set(cardanoRpcUid, { resolve: resolve, reject: reject }); + cardanoRpcUid += 1; + }); } - const cardano = Object.freeze(new CardanoAPI()) - - function cardano_request_read_access() { + function cardano_request_read_access(cardanoAccessRequest) { + const { requestIdentification, onlySilent } = (cardanoAccessRequest || {}); return new Promise(function(resolve, reject) { window.postMessage({ type: "connector_connect_request/cardano", + requestIdentification, + onlySilent, }, location.origin); connectRequests.push({ - resolve: () => { resolve(cardano); }, + protocol: 'cardano', + resolve: (auth) => { + resolve(Object.freeze(new CardanoAPI(auth, cardano_rpc_call))); + }, reject: reject }); }); @@ -132,17 +105,131 @@ const initialInject = ` } window.cardano = { + ...(window.cardano||{}), '${WALLET_NAME}': { enable: cardano_request_read_access, isEnabled: cardano_check_read_access, - version: '${API_VERSION}', + apiVersion: '${API_VERSION}', name: '${WALLET_NAME}', } }; })(); ` -// client-facing ergo object API +const cardanoApiInject = ` +class CardanoAuth { + constructor(auth, rpc) { + this._auth = auth; + this._cardano_rpc_call = rpc; + } + + isEnabled() { + return this._auth != null; + } + + getWalletId() { + if (!this._auth) { + throw new Error('This connection does not have auth enabled!'); + } + return this._auth.walletId; + } + + getWalletPubkey() { + if (!this._auth) { + throw new Error('This connection does not have auth enabled!'); + } + return this._auth.pubkey; + } + + signHexPayload(payload_hex_string) { + if (!this._auth) { + throw new Error('This connection does not have auth enabled!'); + } + return this._cardano_rpc_call("auth_sign_hex_payload/cardano", [payload_hex_string]); + } + + checkHexPayload(payload_hex_string, signature_hex_string) { + if (!this._auth) { + throw new Error('This connection does not have auth enabled!'); + } + return this._cardano_rpc_call("auth_check_hex_payload/cardano", [payload_hex_string, signature_hex_string]); + } +} +class CardanoAPI { + + constructor(auth, rpc) { + this._auth = new CardanoAuth(auth, rpc); + this._cardano_rpc_call = rpc; + this._disconnection = [false]; + const self = this; + window.addEventListener('yoroi_wallet_disconnected', function() { + if (!self._disconnection[0]) { + self._disconnection[0] = true; + self._disconnection.slice(1).forEach(f => f()); + } + }); + } + + getNetworkId() { + // TODO + throw new Error('Not implemented yet'); + } + + auth() { + return this._auth; + } + + getBalance(token_id = 'ADA') { + return this._cardano_rpc_call("get_balance", [token_id]); + } + + getUsedAddresses(paginate = undefined) { + return this._cardano_rpc_call("get_used_addresses", [paginate]); + } + + getUnusedAddresses() { + return this._cardano_rpc_call("get_unused_addresses", []); + } + + getRewardAddresses() { + // TODO + throw new Error('Not implemented yet'); + } + + getChangeAddress() { + return this._cardano_rpc_call("get_change_address", []); + } + + getUtxos(amount = undefined, token_id = 'ADA', paginate = undefined) { + return this._cardano_rpc_call("get_utxos", [amount, token_id, paginate]); + } + + submitTx(tx) { + return this._cardano_rpc_call('submit_tx', [tx]); + } + + signTx(tx, partialSign = false) { + return this._cardano_rpc_call('sign_tx/cardano', [{ tx, partialSign }]); + } + + signData(address, sigStructure) { + // TODO + throw new Error('Not implemented yet'); + } + + createTx(req) { + return this._cardano_rpc_call("create_tx/cardano", [req]); + } + + onDisconnect(callback) { + if (this._disconnection[0]) { + throw new Error('Cardano API instance is already disconnected!'); + } + this._disconnection.push(callback); + } +} +` + const ergoApiInject = ` // RPC set-up var ergoRpcUid = 0; @@ -150,7 +237,7 @@ var ergoRpcResolver = new Map(); window.addEventListener("message", function(event) { if (event.data.type == "connector_rpc_response" && event.data.protocol === "ergo") { - console.log("page received from connector: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin); + console.debug("page received from connector: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin); const rpcPromise = ergoRpcResolver.get(event.data.uid); if (rpcPromise !== undefined) { const ret = event.data.return; @@ -212,7 +299,7 @@ class ErgoAPI { function: func, params: params }, location.origin); - console.log("ergoRpcUid = " + ergoRpcUid); + console.debug("ergoRpcUid = " + ergoRpcUid); ergoRpcResolver.set(ergoRpcUid, { resolve: resolve, reject: reject }); ergoRpcUid += 1; }); @@ -222,10 +309,6 @@ class ErgoAPI { const ergo = Object.freeze(new ErgoAPI()); ` -const cardanoApiInject = ` - -` - const API_INTERNAL_ERROR = -2; const API_REFUSED = -3; @@ -272,12 +355,17 @@ let yoroiPort = null; let ergoApiInjected = false; let cardanoApiInjected = false; -function disconnectWallet() { +function disconnectWallet(protocol) { yoroiPort = null; - window.dispatchEvent(new Event("ergo_wallet_disconnected")); + if (protocol === 'ergo') { + window.dispatchEvent(new Event("ergo_wallet_disconnected")); + } else { + window.dispatchEvent(new Event("yoroi_wallet_disconnected")); + } } function createYoroiPort() { + const connectedProtocolHolder = []; // events from Yoroi if (extensionId === 'self') { // this is part of Yoroi extension @@ -292,6 +380,7 @@ function createYoroiPort() { window.postMessage(message, location.origin); } else if (message.type === "yoroi_connect_response/ergo") { if (message.success) { + connectedProtocolHolder[0] = 'ergo'; if (!ergoApiInjected) { // inject full API here if (injectIntoPage(ergoApiInject)) { @@ -314,17 +403,34 @@ function createYoroiPort() { }, location.origin); } else if (message.type === "yoroi_connect_response/cardano") { if (message.success) { - cardanoApiInjected = true; + connectedProtocolHolder[0] = 'cardano'; + if (!cardanoApiInjected) { + // inject full API here + if (injectIntoPage(cardanoApiInject)) { + cardanoApiInjected = true; + } else { + console.error() + window.postMessage({ + type: "connector_connected", + err: { + code: API_INTERNAL_ERROR, + info: "failed to inject Cardano API" + } + }, location.origin); + } + } } window.postMessage({ type: "connector_connected", - success: message.success + success: message.success, + auth: message.auth, + err: message.err, }, location.origin); } }); yoroiPort.onDisconnect.addListener(event => { - disconnectWallet(); + disconnectWallet(connectedProtocolHolder[0]); }); } @@ -336,7 +442,7 @@ if (shouldInject()) { window.addEventListener("message", function(event) { const dataType = event.data.type; if (dataType === "connector_rpc_request") { - console.log("connector received from page: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin); + console.debug("connector received from page: " + JSON.stringify(event.data) + " with source = " + event.source + " and origin = " + event.origin); if (yoroiPort) { try { yoroiPort.postMessage(event.data); @@ -367,7 +473,8 @@ if (shouldInject()) { }, location.origin); } } else if (dataType === "connector_connect_request/ergo" || dataType === 'connector_connect_request/cardano') { - if ((ergoApiInjected || cardanoApiInjected) && yoroiPort) { + const requestIdentification = event.data.requestIdentification; + if ((ergoApiInjected || (cardanoApiInjected && !requestIdentification)) && yoroiPort) { // we can skip communication - API injected + hasn't been disconnected console.log('you are already connected') window.postMessage({ @@ -386,7 +493,11 @@ if (shouldInject()) { const message = { imgBase64Url, type: `yoroi_connect_request/${protocol}`, - url: location.hostname, + connectParameters: { + url: location.hostname, + requestIdentification, + onlySilent: event.data.onlySilent, + }, protocol, }; yoroiPort.postMessage(message); diff --git a/packages/yoroi-extension/app/App.js b/packages/yoroi-extension/app/App.js index 2729453bbd..a971be19cf 100644 --- a/packages/yoroi-extension/app/App.js +++ b/packages/yoroi-extension/app/App.js @@ -88,6 +88,8 @@ class App extends Component { translations[locale] ); + Logger.debug(`[yoroi] messages merged`); + const themeVars = Object.assign(stores.profile.currentThemeVars, { // show wingdings on dev builds when no font is set to easily find // missing font bugs. However, on production, we use Times New Roman @@ -100,6 +102,8 @@ class App extends Component { const muiTheme = MuiThemes[currentTheme]; + Logger.debug(`[yoroi] themes changed`); + return (
diff --git a/packages/yoroi-extension/app/api/localStorage/index.js b/packages/yoroi-extension/app/api/localStorage/index.js index fc38fb778d..b07897d665 100644 --- a/packages/yoroi-extension/app/api/localStorage/index.js +++ b/packages/yoroi-extension/app/api/localStorage/index.js @@ -202,7 +202,9 @@ export default class LocalStorageApi { getWhitelist: void => Promise> = async () => { const result = await getLocalItem(storageKeys.ERGO_CONNECTOR_WHITELIST); if (result === undefined || result === null) return undefined; - return JSON.parse(result); + const filteredWhitelist = JSON.parse(result).filter(e => e.protocol != null); + this.setWhitelist(filteredWhitelist); + return filteredWhitelist; } setWhitelist: (Array | void) => Promise = value => diff --git a/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/ConnectedWebsitesPage.js b/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/ConnectedWebsitesPage.js index 4e1a363c79..118e1236c2 100644 --- a/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/ConnectedWebsitesPage.js +++ b/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/ConnectedWebsitesPage.js @@ -1,26 +1,24 @@ // @flow -import { Component } from 'react'; import type { Node } from 'react'; +import { Component } from 'react'; import { observer } from 'mobx-react'; -import type { WhitelistEntry, PublicDeriverCache } from '../../../../chrome/extension/ergo-connector/types' +import type { PublicDeriverCache, WhitelistEntry } from '../../../../chrome/extension/ergo-connector/types' import styles from './ConnectedWebsitesPage.scss' import NoItemsFoundImg from '../../../assets/images/dapp-connector/no-websites-connected.inline.svg' -import { intlShape, defineMessages } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; +import { defineMessages, intlShape } from 'react-intl'; import { connectorMessages } from '../../../i18n/global-messages'; import { isErgo } from '../../../api/ada/lib/storage/database/prepackaged/networks'; import WalletRow from './WalletRow'; import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; -import type { - TokenLookupKey, -} from '../../../api/common/lib/MultiToken'; +import type { TokenLookupKey, } from '../../../api/common/lib/MultiToken'; import type { ConceptualWalletSettingsCache } from '../../../stores/toplevel/WalletSettingsStore'; type Props = {| +whitelistEntries: ?Array, +activeSites: Array, +wallets: ?Array, - +onRemoveWallet: ?string => void, + +onRemoveWallet: {| url: ?string, protocol: ?string |} => void, +getTokenInfo: $ReadOnly> => $ReadOnly, +shouldHideBalance: boolean, +getConceptualWallet: number => ConceptualWalletSettingsCache | null @@ -37,48 +35,6 @@ const messages = defineMessages({ }, }); -function walletExistInWebsitsList( - whitelistEntries: Array, - publicDeriverId: number) { - for(const website of whitelistEntries) { - if (website.publicDeriverId === publicDeriverId) return true - } - return false -} - -function checkForNetworks( - wallets: Array, - whitelistEntries: Array - ) { - /** - * Form a list of cached wallets. will look if the list has ergo wallets or cardano wallts - * or both. - */ - let isErgoExist = false - let isCardanoExist = false - - for (const wallet of wallets) { - if(!walletExistInWebsitsList(whitelistEntries, wallet.publicDeriver.getPublicDeriverId())) { - continue - } - if (isErgo(wallet.publicDeriver.getParent().getNetworkInfo())) { - isErgoExist = true - } else { - isCardanoExist = true - } - // if both networks exists in the set of wallet we don't need to continue searching - if (isErgoExist && isCardanoExist ) return { - isErgoExist, - isCardanoExist - } - } - - return { - isErgoExist, - isCardanoExist - } -} - @observer export default class ConnectedWebsitesPage extends Component { static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { @@ -97,14 +53,47 @@ export default class ConnectedWebsitesPage extends Component {
); - if (this.props.whitelistEntries == null || this.props.wallets == null) { - return genNoResult(); - } const { whitelistEntries, wallets } = this.props; - if (whitelistEntries.length === 0) { - return genNoResult(); + if (whitelistEntries == null + || whitelistEntries.length === 0 + || wallets == null + || wallets.length === 0 + ) { + return genNoResult(); } - const { isCardanoExist, isErgoExist } = checkForNetworks(wallets, whitelistEntries) + + const { ergoNodes, cardanoNodes } = whitelistEntries.map(( + { url, protocol, publicDeriverId, image } + ) => { + const wallet = wallets.find( cacheEntry => + cacheEntry.publicDeriver.getPublicDeriverId() === publicDeriverId + ) + if (wallet == null) { + return [null, null] + } + return [isErgo(wallet.publicDeriver.getParent().getNetworkInfo()), ( + + )] + }).reduce((acc, [isWalletErgo, node]) => { + if (node != null) { + acc[isWalletErgo ? 'ergoNodes' : 'cardanoNodes'].push(node); + } + return acc; + }, { ergoNodes: [], cardanoNodes: [] }); + + + return (
@@ -113,68 +102,22 @@ export default class ConnectedWebsitesPage extends Component {

Dapps

- {isCardanoExist && + {cardanoNodes.length > 0 &&

Cardano, ADA

{ - whitelistEntries.map(({ url, publicDeriverId, image }) => { - const wallet = wallets.find( cacheEntry => - cacheEntry.publicDeriver.getPublicDeriverId() === publicDeriverId - ) - if (wallet == null) { - return null - } - if (!isErgo(wallet.publicDeriver.getParent().getNetworkInfo())) { - return ( - - ) - } - return '' - }) + cardanoNodes }
} - {isErgoExist && + {ergoNodes.length > 0 &&

Ergo, ERG

{ - whitelistEntries.map(({ url, publicDeriverId, image }) => { - const wallet = wallets.find( cacheEntry => - cacheEntry.publicDeriver.getPublicDeriverId() === publicDeriverId - ) - if (wallet == null) { - return null - } - if (isErgo(wallet.publicDeriver.getParent().getNetworkInfo())) { - return ( - - ) - } - return '' - }) + ergoNodes }
}
diff --git a/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/WalletRow.js b/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/WalletRow.js index f5fc68c461..6efefd1e5a 100644 --- a/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/WalletRow.js +++ b/packages/yoroi-extension/app/components/dapp-connector/ConnectedWebsites/WalletRow.js @@ -26,10 +26,11 @@ const messages = defineMessages({ type Props = {| +url: ?string, + +protocol: ?string, +isActiveSite: boolean, +wallet: PublicDeriverCache, +shouldHideBalance: boolean, - +onRemoveWallet: ?string => void, + +onRemoveWallet: {| url: ?string, protocol: ?string |} => void, +getTokenInfo: $ReadOnly> => $ReadOnly, +settingsCache: ConceptualWalletSettingsCache | null, +websiteIcon: string, @@ -77,6 +78,7 @@ export default class WalletRow extends Component { const { isActiveSite, url, + protocol, wallet, onRemoveWallet, shouldHideBalance, @@ -134,7 +136,7 @@ export default class WalletRow extends Component {
{showDeleteIcon && - }
diff --git a/packages/yoroi-extension/app/components/layout/TopBarLayout.js b/packages/yoroi-extension/app/components/layout/TopBarLayout.js index bf159c1207..0f0b4fecb3 100644 --- a/packages/yoroi-extension/app/components/layout/TopBarLayout.js +++ b/packages/yoroi-extension/app/components/layout/TopBarLayout.js @@ -44,6 +44,7 @@ function TopBarLayout({ sx={{ position: 'relative', overflow: 'auto', + height: '100%', '&::-webkit-scrollbar-button': { height: '7px', display: 'block', @@ -161,8 +162,8 @@ function TopBarLayout({ width: '100%', height: '100%', overflow: 'hidden', - display: 'grid', - gridTemplateRows: 'auto 1fr', + display: 'flex', + flexDirection: 'column', position: 'relative', background: showInContainer === true && 'var(--yoroi-palette-gray-50)', }} diff --git a/packages/yoroi-extension/app/components/loading/Loading.js b/packages/yoroi-extension/app/components/loading/Loading.js index 2b279636ed..aa8825767c 100644 --- a/packages/yoroi-extension/app/components/loading/Loading.js +++ b/packages/yoroi-extension/app/components/loading/Loading.js @@ -51,28 +51,24 @@ export default class Loading extends Component { styles.yoroiLogo, hasLoadedCurrentTheme ? null : styles.hide, ]); - const renderError = error == null || !hasLoadedCurrentLocale - ? null - : ( -
-

- {intl.formatMessage(error)}

- {this._getErrorMessageComponent()} + const renderError = error != null && hasLoadedCurrentLocale ? ( +
+

+ {intl.formatMessage(error)}

+ {this._getErrorMessageComponent()} +

+
+ ) : null; + const renderContent = error == null && isLoadingDataForNextScreen ? ( +
+ {hasLoadedCurrentLocale && ( +

+ {intl.formatMessage(messages.loading)}

-
- ); - const renderContent = (error != null || !isLoadingDataForNextScreen) - ? null - : ( -
- {hasLoadedCurrentLocale && ( -

- {intl.formatMessage(messages.loading)} -

- )} - -
- ); + )} + +

+ ) : null; return (
diff --git a/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js b/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js index 68aca3242d..a8d10dc500 100644 --- a/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js +++ b/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js @@ -127,9 +127,8 @@ class ThemeSettingsBlock extends Component { ); - // need to enable nightly const shouldDisplayRevampButton = environment.isDev() - // || environment.isNightly() + || environment.isNightly() || environment.isTest(); const themeBlockClassicComponent = ( diff --git a/packages/yoroi-extension/app/containers/dapp-connector/ConnectedWebsitesContainer.js b/packages/yoroi-extension/app/containers/dapp-connector/ConnectedWebsitesContainer.js index 05680acecf..1937b667c7 100644 --- a/packages/yoroi-extension/app/containers/dapp-connector/ConnectedWebsitesContainer.js +++ b/packages/yoroi-extension/app/containers/dapp-connector/ConnectedWebsitesContainer.js @@ -55,11 +55,14 @@ class ConnectedWebsitesPageContainer extends Component { await this.generated.actions.connector.getConnectorWhitelist.trigger(); } - onRemoveWallet: ?string => void = url => { - if (url == null) { - throw new Error(`Removing a wallet from whitelist but there's no url`); + onRemoveWallet: {| url: ?string, protocol: ?string |} => void = ({ url, protocol }) => { + if (url == null || protocol == null) { + throw new Error(`Removing a wallet from whitelist but there's no url or protocol`); } - this.generated.actions.connector.removeWalletFromWhitelist.trigger(url); + this.generated.actions.connector.removeWalletFromWhitelist.trigger({ + url, + protocol, + }); }; getConceptualWallet(publicDeriverId: number): ConceptualWalletSettingsCache | null { const wallets = this.generated.stores.wallets.publicDerivers; @@ -139,7 +142,7 @@ class ConnectedWebsitesPageContainer extends Component { trigger: (params: void) => Promise, |}, removeWalletFromWhitelist: {| - trigger: (params: string) => Promise, + trigger: (params: {| url: string, protocol: string |}) => Promise, |}, getConnectorWhitelist: {| trigger: (params: void) => Promise, diff --git a/packages/yoroi-extension/app/ergo-connector/actions/connector-actions.js b/packages/yoroi-extension/app/ergo-connector/actions/connector-actions.js index c877e07998..c3978e3e17 100644 --- a/packages/yoroi-extension/app/ergo-connector/actions/connector-actions.js +++ b/packages/yoroi-extension/app/ergo-connector/actions/connector-actions.js @@ -13,7 +13,10 @@ export default class ConnectorActions { updateConnectorWhitelist: AsyncAction<{| whitelist: Array, |}> = new AsyncAction(); - removeWalletFromWhitelist: AsyncAction = new AsyncAction(); + removeWalletFromWhitelist: AsyncAction<{| + url: string, + protocol: string, + |}> = new AsyncAction(); confirmSignInTx: Action = new Action(); cancelSignInTx: Action = new Action(); } diff --git a/packages/yoroi-extension/app/ergo-connector/api/index.js b/packages/yoroi-extension/app/ergo-connector/api/index.js new file mode 100644 index 0000000000..342193a8f2 --- /dev/null +++ b/packages/yoroi-extension/app/ergo-connector/api/index.js @@ -0,0 +1,57 @@ +// @flow + +import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver'; +import type { WalletChecksum } from '@emurgo/cip4-js'; +import type { WalletAuthEntry } from '../../../chrome/extension/ergo-connector/types'; +import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; +import blake2b from 'blake2b'; + + +export const createAuthEntry: ({| + appAuthID: ?string, + deriver: PublicDeriver<>, + checksum: ?WalletChecksum, +|}) => Promise = async ({ appAuthID, deriver, checksum }) => { + if (appAuthID == null) { + return null; + } + if (checksum == null) { + throw new Error(`[createAuthEntry] app auth is requested but wallet-checksum does not exist`) + } + // this is a temporary insecure dev stub using the deriver public key + // $FlowFixMe[prop-missing] + const walletPubKey = (await deriver.getPublicKey()).Hash; + const appPubKey = RustModule.WalletV4.Bip32PrivateKey.from_bip39_entropy( + Buffer.from(walletPubKey, 'hex'), + Buffer.from( + blake2b(64) + .update(Buffer.from(appAuthID)) + .digest('binary'), + ), + ).to_raw_key().to_public(); + return { + walletId: checksum.ImagePart, + pubkey: Buffer.from(appPubKey.as_bytes()).toString('hex'), + }; +}; + +export const authSignHexPayload: ({| + appAuthID: ?string, + deriver: PublicDeriver<>, + payloadHex: string, +|}) => Promise = async ({ appAuthID, deriver, payloadHex }) => { + if (appAuthID == null) { + throw new Error(`[authSignHexPayload] app auth sign is requested but no auth is present in connection`) + } + // $FlowFixMe[prop-missing] + const walletPubKey = (await deriver.getPublicKey()).Hash; + const appPrivKey = RustModule.WalletV4.Bip32PrivateKey.from_bip39_entropy( + Buffer.from(walletPubKey, 'hex'), + Buffer.from( + blake2b(64) + .update(Buffer.from(appAuthID)) + .digest('binary'), + ), + ).to_raw_key(); + return appPrivKey.sign(Buffer.from(payloadHex, 'hex')).to_hex(); +} \ No newline at end of file diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect-websites/ConnectWebsitesPage.js b/packages/yoroi-extension/app/ergo-connector/components/connect-websites/ConnectWebsitesPage.js index 50cce6ea55..b8ded00987 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect-websites/ConnectWebsitesPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect-websites/ConnectWebsitesPage.js @@ -20,7 +20,7 @@ type Props = {| +whitelistEntries: ?Array, +activeSites: Array, +wallets: ?Array, - +onRemoveWallet: ?string => void, + +onRemoveWallet: (url: string, protocol: string) => void, +getTokenInfo: $ReadOnly> => $ReadOnly, +shouldHideBalance: boolean, |}; @@ -63,7 +63,7 @@ export default class ConnectWebsitesPage extends Component { {intl.formatMessage(connectorMessages.connectedWebsites)}
- {whitelistEntries.map(({ url, publicDeriverId }) => { + {whitelistEntries.map(({ url, publicDeriverId, protocol }) => { const wallet = wallets.find( cacheEntry => cacheEntry.publicDeriver.getPublicDeriverId() === publicDeriverId ); @@ -78,7 +78,7 @@ export default class ConnectWebsitesPage extends Component { url={url} isActiveSite={this.props.activeSites.includes(url)} wallet={wallet} - onRemoveWallet={this.props.onRemoveWallet} + onRemoveWallet={() => { this.props.onRemoveWallet(url, protocol) }} getTokenInfo={this.props.getTokenInfo} shouldHideBalance={this.props.shouldHideBalance} /> diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js index de244b9ecb..5063ffd2b3 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js @@ -21,6 +21,8 @@ import type { TokenLookupKey } from '../../../api/common/lib/MultiToken'; import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; import { environment } from '../../../environment'; import CheckboxLabel from '../../../components/common/CheckboxLabel'; +import type { WalletChecksum } from '@emurgo/cip4-js'; +import { PublicDeriver } from '../../../api/ada/lib/storage/models/PublicDeriver'; const messages = defineMessages({ subtitle: { @@ -46,9 +48,8 @@ type Props = {| +loading: $Values, +error: string, +message: ?ConnectingMessage, - +onToggleCheckbox: number => void, + +onToggleCheckbox: (PublicDeriver<>, ?WalletChecksum) => void, +onCancel: () => void, - +onConnect: number => Promise, +handleSubmit: () => void, +selected: number, +getTokenInfo: $ReadOnly> => $ReadOnly, @@ -125,7 +126,10 @@ class ConnectPage extends Component { getTokenInfo={this.props.getTokenInfo} /> } - onChange={() => onToggleCheckbox(item.publicDeriver.getPublicDeriverId())} + onChange={() => onToggleCheckbox( + item.publicDeriver, + item.checksum, + )} checked={selected === item.publicDeriver.getPublicDeriverId()} /> diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js index 823522d85c..5be029f27f 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js @@ -15,12 +15,17 @@ import { LoadingWalletStates } from '../types'; import { networks } from '../../api/ada/lib/storage/database/prepackaged/networks'; import { genLookupOrFail, } from '../../stores/stateless/tokenHelpers'; import type { TokenInfoMap } from '../../stores/toplevel/TokenInfoStore'; +import type { WalletChecksum } from '@emurgo/cip4-js'; +import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver'; +import { createAuthEntry } from '../api'; type GeneratedData = typeof ConnectContainer.prototype.generated; declare var chrome; type State = {| selected: number, + deriver: ?PublicDeriver<>, + checksum: ?WalletChecksum, |}; @observer @@ -30,6 +35,8 @@ export default class ConnectContainer extends Component< > { state: State = { selected: -1, + deriver: null, + checksum: null, }; onUnload: (SyntheticEvent<>) => void = ev => { @@ -42,7 +49,8 @@ export default class ConnectContainer extends Component< }); }; - componentDidMount() { + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { this.generated.actions.connector.refreshWallets.trigger(); this.generated.actions.connector.getConnectorWhitelist.trigger(); window.addEventListener('unload', this.onUnload); @@ -51,35 +59,55 @@ export default class ConnectContainer extends Component< componentWillUnmount() { window.removeEventListener('unload', this.onUnload); } - onToggleCheckbox: (index: number) => void = index => { + + onToggleCheckbox: (deriver: PublicDeriver<>, checksum: ?WalletChecksum) => void = (deriver, checksum) => { + const index = deriver.getPublicDeriverId(); this.setState((prevState) => prevState.selected === index - ? { selected: -1 } - : { selected: index } + ? { selected: -1, deriver: null, checksum: null } + : { selected: index, deriver, checksum } ); }; - async onConnect(publicDeriverId: number) { + async onConnect(deriver: PublicDeriver<>, checksum: ?WalletChecksum) { const chromeMessage = this.generated.stores.connector.connectingMessage; if(chromeMessage == null) { throw new Error(`${nameof(chromeMessage)} connecting to a wallet but no connect message found`); } + + const connector = this.generated.actions.connector; + + const url = chromeMessage.url; + const protocol = chromeMessage.protocol; + const appAuthID = chromeMessage.appAuthID; + + const authEntry = await createAuthEntry({ appAuthID, deriver, checksum }); + + const publicDeriverId = deriver.getPublicDeriverId(); const result = this.generated.stores.connector.currentConnectorWhitelist; - const whitelist = result.length ? [...result] : []; + + // Removing any previous whitelisted connections for the same url + const whitelist = (result.length ? [...result] : []) + .filter(e => (e.protocol !== protocol) || (e.url !== url)); + whitelist.push({ - url: chromeMessage.url, + url, + protocol, publicDeriverId, - image: chromeMessage.imgBase64Url + appAuthID, + auth: authEntry, + image: chromeMessage.imgBase64Url, }); - await this.generated.actions.connector.updateConnectorWhitelist.trigger({ whitelist }); + await connector.updateConnectorWhitelist.trigger({ whitelist }); chrome.runtime.sendMessage(({ type: 'connect_response', accepted: true, publicDeriverId, + auth: authEntry, tabId: chromeMessage.tabId, }: ConnectResponseData)); - this.generated.actions.connector.closeWindow.trigger(); + connector.closeWindow.trigger(); } onCancel: void => void = () => { @@ -96,9 +124,9 @@ export default class ConnectContainer extends Component< handleSubmit: () => void = () => { const wallets = this.generated.stores.connector.filteredWallets; if (wallets) { - const { selected } = this.state; - if (selected >= 0) { - this.onConnect(selected); + const { selected, deriver, checksum } = this.state; + if (selected >= 0 && deriver) { + this.onConnect(deriver, checksum); } } }; @@ -127,7 +155,6 @@ export default class ConnectContainer extends Component< error={error} message={responseMessage} publicDerivers={wallets} - onConnect={this.onConnect} onToggleCheckbox={this.onToggleCheckbox} onCancel={this.onCancel} handleSubmit={this.handleSubmit} diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.js index df48406794..e16d575e14 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.js @@ -29,11 +29,13 @@ export default class ConnectWebsitesContainer extends Component< await this.generated.actions.connector.getConnectorWhitelist.trigger(); } - onRemoveWallet: ?string => void = url => { + onRemoveWallet: (url: string, protocol: string) => void = (url, protocol) => { if (url == null) { throw new Error(`Removing a wallet from whitelist but there's no url`); } - this.generated.actions.connector.removeWalletFromWhitelist.trigger(url); + this.generated.actions.connector.removeWalletFromWhitelist.trigger({ + url, protocol + }); }; render(): Node { @@ -83,7 +85,7 @@ export default class ConnectWebsitesContainer extends Component< trigger: (params: void) => Promise, |}, removeWalletFromWhitelist: {| - trigger: (params: string) => Promise, + trigger: (params: {| url: string, protocol: string |}) => Promise, |}, getConnectorWhitelist: {| trigger: (params: void) => Promise, diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.stories.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.stories.js index 96e30723b5..bcbe2e415a 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.stories.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectWebsitesContainer.stories.js @@ -122,11 +122,17 @@ export const Whitelisted = (): Node => { { url: 'google.com', publicDeriverId: 0, + appAuthID: '1', + auth: null, + protocol: 'ergo', image: '', }, { url: 'yoroi.com', publicDeriverId: 1, + appAuthID: '2', + auth: null, + protocol: 'cardano', image: '', }, ], diff --git a/packages/yoroi-extension/app/ergo-connector/stores/ConnectorStore.js b/packages/yoroi-extension/app/ergo-connector/stores/ConnectorStore.js index d60241d69c..b370a14193 100644 --- a/packages/yoroi-extension/app/ergo-connector/stores/ConnectorStore.js +++ b/packages/yoroi-extension/app/ergo-connector/stores/ConnectorStore.js @@ -5,36 +5,35 @@ import { observable, action, runInAction, computed, toJS } from 'mobx'; import Request from '../../stores/lib/LocalizedRequest'; import Store from '../../stores/base/Store'; import type { - PublicDeriverCache, ConfirmedSignData, - ConnectingMessage, - FailedSignData, - SigningMessage, - WhitelistEntry, ConnectedSites, + ConnectingMessage, ConnectRetrieveData, - TxSignWindowRetrieveData, - RemoveWalletFromWhitelistData, + FailedSignData, GetConnectedSitesData, Protocol, + PublicDeriverCache, + RemoveWalletFromWhitelistData, + SigningMessage, Tx, + TxSignWindowRetrieveData, + WhitelistEntry, } from '../../../chrome/extension/ergo-connector/types'; import type { ActionsMap } from '../actions/index'; import type { StoresMap } from './index'; +import type { CardanoConnectorSignRequest } from '../types'; import { LoadingWalletStates } from '../types'; +import { getWallets } from '../../api/common/index'; import { - getWallets -} from '../../api/common/index'; -import { + getCardanoHaskellBaseConfig, + getErgoBaseConfig, isCardanoHaskell, isErgo, - getErgoBaseConfig, - getCardanoHaskellBaseConfig, } from '../../api/ada/lib/storage/database/prepackaged/networks'; import { + asGetAllUtxos, asGetBalance, asGetPublicKey, - asGetAllUtxos, asHasUtxoChains, } from '../../api/ada/lib/storage/models/PublicDeriver/traits'; import { MultiToken } from '../../api/common/lib/MultiToken'; @@ -46,10 +45,7 @@ import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; import { toRemoteUtxo } from '../../api/ergo/lib/transactions/utils'; import { mintedTokenInfo } from '../../../chrome/extension/ergo-connector/utils'; import { Logger } from '../../utils/logging'; -import type { CardanoConnectorSignRequest } from '../types'; -import { - asAddressedUtxo, -} from '../../api/ada/transactions/utils'; +import { asAddressedUtxo, } from '../../api/ada/transactions/utils'; import { genTimeToSlot, } from '../../api/ada/lib/storage/bridge/timeUtils'; import { connectorGetUsedAddresses, @@ -253,11 +249,13 @@ export default class ConnectorStore extends Store { runInAction(() => { this.signingMessage = response; }); - if (response && response.sign.type === 'tx/cardano') { - this.createAdaTransaction(); - } - if (response.sign.type === 'tx-create-req/cardano') { - this.generateAdaTransaction(); + if (response) { + if (response.sign.type === 'tx/cardano') { + this.createAdaTransaction(); + } + if (response.sign.type === 'tx-create-req/cardano') { + this.generateAdaTransaction(); + } } }) // eslint-disable-next-line no-console @@ -685,15 +683,19 @@ export default class ConnectorStore extends Store { await this.setConnectorWhitelist.execute({ whitelist }); await this.getConnectorWhitelist.execute(); }; - _removeWalletFromWhitelist: (url: string) => Promise = async url => { - const filter = this.currentConnectorWhitelist.filter(e => e.url !== url); + _removeWalletFromWhitelist: ( + request: {| url: string, protocol: string |} + ) => Promise = async (request) => { + const filter = this.currentConnectorWhitelist.filter( + e => !(e.url === request.url && e.protocol === request.protocol) + ); await this.setConnectorWhitelist.execute({ whitelist: filter, }); await this.getConnectorWhitelist.execute(); window.chrome.runtime.sendMessage(({ type: 'remove_wallet_from_whitelist', - url, + url: request.url, }: RemoveWalletFromWhitelistData)); }; diff --git a/packages/yoroi-extension/app/stores/DappConnectorStore.js b/packages/yoroi-extension/app/stores/DappConnectorStore.js index c69cd860bd..f1d072d082 100644 --- a/packages/yoroi-extension/app/stores/DappConnectorStore.js +++ b/packages/yoroi-extension/app/stores/DappConnectorStore.js @@ -187,15 +187,19 @@ export default class ConnectorStore extends Store { await this.setConnectorWhitelist.execute({ whitelist }); await this.getConnectorWhitelist.execute(); }; - _removeWalletFromWhitelist: (url: string) => Promise = async url => { - const filter = this.currentConnectorWhitelist.filter(e => e.url !== url); + _removeWalletFromWhitelist: ( + request: {| url: string, protocol: string |} + ) => Promise = async request => { + const filter = this.currentConnectorWhitelist.filter( + e => !(e.url === request.url && e.protocol === request.protocol) + ); await this.setConnectorWhitelist.execute({ whitelist: filter, }); await this.getConnectorWhitelist.execute(); window.chrome.runtime.sendMessage(({ type: 'remove_wallet_from_whitelist', - url, + url: request.url, }: RemoveWalletFromWhitelistData)); }; diff --git a/packages/yoroi-extension/app/stores/base/BaseLoadingStore.js b/packages/yoroi-extension/app/stores/base/BaseLoadingStore.js index 961a141eed..704110164d 100644 --- a/packages/yoroi-extension/app/stores/base/BaseLoadingStore.js +++ b/packages/yoroi-extension/app/stores/base/BaseLoadingStore.js @@ -43,18 +43,23 @@ export default class BaseLoadingStore extends Store { + Logger.debug(`[yoroi] closing other instances`); await closeOtherInstances(this.getTabIdKey.bind(this)()); + Logger.debug(`[yoroi] loading persistent db`); const persistentDb = this.loadPersistentDbRequest.result; if (persistentDb == null) throw new Error(`${nameof(BaseLoadingStore)}::${nameof(this.load)} load db was not loaded. Should never happen`); + Logger.debug(`[yoroi] check migrations`); await this.migrationRequest.execute({ localStorageApi: this.api.localStorage, persistentDb, currVersion: environment.getVersion(), }).promise; + Logger.debug(`[yoroi][preLoadingScreenEnd]`); await this.preLoadingScreenEnd.bind(this)(); runInAction(() => { this.error = null; this._loading = false; + Logger.debug(`[yoroi] loading ended`); }); return undefined; }).catch((error) => { diff --git a/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js b/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js index 3249867fb7..c198bb4ef6 100644 --- a/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js +++ b/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js @@ -104,7 +104,7 @@ export const DAPP_CONNECTOR: SidebarCategory = registerCategory({ // We only added it as all these labels of the sidebar are passed // to intl.formatMessage(...) -> we have to pass valid label. label: connectorMessages.dappConnector, - isVisible: _request => !environment.isLight + isVisible: _request => !environment.isLight, }); export const NOTICE_BOARD: SidebarCategory = registerCategory({ diff --git a/packages/yoroi-extension/chrome/extension/background.js b/packages/yoroi-extension/chrome/extension/background.js index e27a2f055b..da5d2735c7 100644 --- a/packages/yoroi-extension/chrome/extension/background.js +++ b/packages/yoroi-extension/chrome/extension/background.js @@ -27,6 +27,8 @@ import type { Tx, CardanoTx, GetConnectionProtocolData, + WhitelistEntry, + WalletAuthEntry, } from './ergo-connector/types'; import { APIErrorCodes, @@ -57,7 +59,7 @@ import { RemoteFetcher } from '../../app/api/ergo/lib/state-fetch/remoteFetcher' import { BatchedFetcher } from '../../app/api/ergo/lib/state-fetch/batchedFetcher'; import LocalStorageApi from '../../app/api/localStorage/index'; import { RustModule } from '../../app/api/ada/lib/cardanoCrypto/rustLoader'; -import { Logger } from '../../app/utils/logging'; +import { Logger, stringifyError } from '../../app/utils/logging'; import { schema } from 'lovefield'; import type { lf$Database, @@ -70,6 +72,7 @@ import { migrateNoRefresh } from '../../app/api/common/migration'; import { Mutex, } from 'async-mutex'; import { isCardanoHaskell } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; import type CardanoTxRequest from '../../app/api/ada'; +import { authSignHexPayload } from '../../app/ergo-connector/api'; /*:: @@ -92,12 +95,18 @@ type PublicDeriverId = number; // PublicDeriverId = successfully connected - which public deriver the user selected // null = refused by user type ConnectedStatus = null | {| - publicDeriverId: PublicDeriverId + publicDeriverId: PublicDeriverId, + auth: ?WalletAuthEntry, |} | {| // response (?PublicDeriverId) - null means the user refused, otherwise the account they selected - resolve: ?PublicDeriverId => void, + resolve: ({| + connectedWallet: ?PublicDeriverId, + auth: ?WalletAuthEntry, + |}) => void, // if a window has fetched this to show to the user yet openedWindow: boolean, + publicDeriverId: null, + auth: null, |}; type PendingSign = {| @@ -114,6 +123,8 @@ let connectionProtocol: string = ''; type ConnectedSite = {| url: string, + protocol: 'cardano' | 'ergo', + appAuthID?: string, status: ConnectedStatus, pendingSigns: Map |}; @@ -290,36 +301,46 @@ async function syncWallet( } } -async function withSelectedWallet( +async function withSelectedSiteConnection( tabId: number, - continuation: PublicDeriver<> => Promise, - db: lf$Database, - localStorageApi: LocalStorageApi, + continuation: ?ConnectedSite => Promise, ): Promise { - const wallets = await getWallets({ db }); const connected = connectedSites.get(tabId); if (connected) { if (typeof connected.status?.publicDeriverId === 'number') { - const { publicDeriverId } = connected.status; - const selectedWallet = wallets.find( - cache => cache.getPublicDeriverId() === publicDeriverId - ); - if (selectedWallet == null) { - connectedSites.delete(tabId); - await removeWallet(tabId, publicDeriverId, localStorageApi); - return Promise.reject(new Error(`Public deriver index not found: ${publicDeriverId}`)); - } - await syncWallet(selectedWallet, localStorageApi); - - // we need to make sure this runs within the withDb call - // since the publicDeriver contains a DB reference inside it - return await continuation(selectedWallet); + return await continuation(Object.freeze(connected)); } return Promise.reject(new Error('site not connected yet')); } return Promise.reject(new Error(`could not find tabId ${tabId} in connected sites`)); } +async function withSelectedWallet( + tabId: number, + continuation: (PublicDeriver<>, ?ConnectedSite) => Promise, + db: lf$Database, + localStorageApi: LocalStorageApi, +): Promise { + const wallets = await getWallets({ db }); + return await withSelectedSiteConnection(tabId, async connected => { + const { publicDeriverId } = connected?.status ?? {}; + const selectedWallet = wallets.find( + cache => cache.getPublicDeriverId() === publicDeriverId + ); + if (selectedWallet == null) { + connectedSites.delete(tabId); + // $FlowFixMe[incompatible-call] + await removeWallet(tabId, publicDeriverId, localStorageApi); + return Promise.reject(new Error(`Public deriver index not found: ${String(publicDeriverId)}`)); + } + await syncWallet(selectedWallet, localStorageApi); + + // we need to make sure this runs within the withDb call + // since the publicDeriver contains a DB reference inside it + return await continuation(selectedWallet, connected); + }); +} + // messages from other parts of Yoroi (i.e. the UI for the connector) const yoroiMessageHandler = async ( request: ( @@ -408,14 +429,18 @@ const yoroiMessageHandler = async ( if (request.tabId == null) return; const { tabId } = request; const connection = connectedSites.get(tabId); - if (connection && connection.status != null && connection.status.resolve != null) { + if (connection?.status?.resolve != null) { if (request.accepted === true) { - connection.status.resolve(request.publicDeriverId); + connection.status.resolve({ + connectedWallet: request.publicDeriverId, + auth: request.auth, + }); connection.status = { publicDeriverId: request.publicDeriverId, + auth: request.auth, }; } else { - connection.status.resolve(null); + connection.status.resolve({ connectedWallet: null, auth: null }); connectedSites.delete(tabId); } } @@ -523,6 +548,8 @@ const yoroiMessageHandler = async ( connection.status.openedWindow = true; sendResponse(({ url: connection.url, + protocol: connection.protocol, + appAuthID: connection.appAuthID, imgBase64Url, tabId, }: ConnectingMessage)); @@ -601,31 +628,66 @@ async function confirmSign( async function confirmConnect( tabId: number, - url: string, + connectParameters: {| + url: string, + requestIdentification?: boolean, + onlySilent?: boolean, + protocol: 'cardano' | 'ergo', + |}, localStorageApi: LocalStorageApi, -): Promise { +): Promise<{| + connectedWallet: ?PublicDeriverId, + auth: ?WalletAuthEntry, +|}> { + const { url, requestIdentification, onlySilent, protocol } = connectParameters; + const isAuthRequested = Boolean(requestIdentification); + const appAuthID = isAuthRequested ? url : undefined; const bounds = await getBoundsForTabWindow(tabId); const whitelist = await localStorageApi.getWhitelist() ?? []; - return new Promise(resolve => { - Logger.info(`whitelist: ${JSON.stringify(whitelist)}`); - const whitelistEntry = whitelist.find(entry => entry.url === url); - if (whitelistEntry !== undefined) { - // we already whitelisted this website, so no need to re-ask the user to confirm - connectedSites.set(tabId, { - url, - status: { - publicDeriverId: whitelistEntry.publicDeriverId, - }, - pendingSigns: new Map() + return new Promise((resolve, reject) => { + try { + Logger.info(`whitelist: ${JSON.stringify(whitelist)}`); + const whitelistEntry = whitelist.find((entry: WhitelistEntry) => { + // Whitelist is only matching if same auth or auth is not requested + const matchingUrl = entry.url === url; + const matchingProtocol = entry.protocol === protocol; + const matchingAuthId = entry.appAuthID === appAuthID; + const isAuthWhitelisted = entry.appAuthID != null; + const isAuthPermitted = isAuthWhitelisted && matchingAuthId; + return matchingUrl && matchingProtocol && (!isAuthRequested || isAuthPermitted); }); - resolve(whitelistEntry.publicDeriverId); - } else { + if (whitelistEntry !== undefined) { + // we already whitelisted this website, so no need to re-ask the user to confirm + connectedSites.set(tabId, { + url, + protocol, + appAuthID, + status: { + publicDeriverId: whitelistEntry.publicDeriverId, + auth: isAuthRequested ? whitelistEntry.auth : undefined, + }, + pendingSigns: new Map() + }); + resolve({ + connectedWallet: whitelistEntry.publicDeriverId, + auth: isAuthRequested ? whitelistEntry.auth : undefined, + }); + return; + } + if (onlySilent) { + reject(new Error('[onlySilent:fail] No active connection')); + return; + } // website not on whitelist, so need to ask user to confirm connection connectedSites.set(tabId, { url, + protocol, + appAuthID, status: { resolve, openedWindow: false, + publicDeriverId: null, + auth: null, }, pendingSigns: new Map() }); @@ -635,6 +697,8 @@ async function confirmConnect( left: (bounds.width + bounds.positionX) - popupProps.width, top: bounds.positionY + 80, }); + } catch (e) { + reject(e); } }); } @@ -676,56 +740,82 @@ function handleInjectorConnect(port) { ports.set(tabId, port); port.onMessage.addListener(async message => { - connectionProtocol = message.protocol; - imgBase64Url = message.imgBase64Url; - function rpcResponse(response) { - port.postMessage({ - type: 'connector_rpc_response', - protocol: message.protocol, - uid: message.uid, - return: response - }); - } - function checkParamCount(expected: number) { - const found = message?.params?.length; - if (found !== expected) { - throw ConnectorError.invalidRequest(`RPC call has ${found} arguments, expected ${expected}`); - } - } - function handleError(e: any) { - if (e instanceof ConnectorError) { - rpcResponse({ - err: e.toAPIError() + connectionProtocol = message.protocol; + imgBase64Url = message.imgBase64Url; + function rpcResponse(response) { + port.postMessage({ + type: 'connector_rpc_response', + protocol: message.protocol, + uid: message.uid, + return: response }); - } else { - const func = message.function; - const args = message.params.map(JSON.stringify).join(', '); - if (e?.stack != null) { - Logger.error(`RPC call ergo.${func}(${args}) failed due to internal error: ${e}\n${e.stack}`); - } else { - Logger.error(`RPC call ergo.${func}(${args}) failed due to internal error: ${e}`); + } + function checkParamCount(expected: number) { + const found = message?.params?.length; + if (found !== expected) { + throw ConnectorError.invalidRequest(`RPC call has ${found} arguments, expected ${expected}`); } - rpcResponse({ - err: { - code: APIErrorCodes.API_INTERNAL_ERROR, - info: 'Yoroi has encountered an internal error - please see logs' + } + function handleError(e: any) { + if (e instanceof ConnectorError) { + rpcResponse({ + err: e.toAPIError() + }); + } else { + const func = message.function; + const args = message.params.map(JSON.stringify).join(', '); + if (e?.stack != null) { + Logger.error(`RPC call ergo.${func}(${args}) failed due to internal error: ${e}\n${e.stack}`); + } else { + Logger.error(`RPC call ergo.${func}(${args}) failed due to internal error: ${e}`); } - }); + rpcResponse({ + err: { + code: APIErrorCodes.API_INTERNAL_ERROR, + info: 'Yoroi has encountered an internal error - please see logs' + } + }); + } } - } - if (message.type.startsWith('yoroi_connect_request')) { - await withDb( - async (_db, localStorageApi) => { - const publicDeriverId = await confirmConnect(tabId, message.url, localStorageApi); - const accepted = publicDeriverId !== null; + const connectParameters = () => ({ + protocol: message.protocol, + ...message.connectParameters, + }); + if (message.type === 'yoroi_connect_request/ergo') { + await withDb( + async (_db, localStorageApi) => { + const { connectedWallet } = + (await confirmConnect(tabId, connectParameters(), localStorageApi)) ?? {}; + const accepted = connectedWallet != null; + port.postMessage({ + type: 'yoroi_connect_response/ergo', + success: accepted + }); + } + ); + } else if (message.type === 'yoroi_connect_request/cardano') { + try { + await withDb( + async (_db, localStorageApi) => { + const { connectedWallet, auth } = + (await confirmConnect(tabId, connectParameters(), localStorageApi)) ?? {}; + const accepted = connectedWallet != null; + port.postMessage({ + type: 'yoroi_connect_response/cardano', + success: accepted, + auth, + }); + } + ); + } catch (e) { port.postMessage({ - type: `yoroi_connect_response/${message.type.split('/')[1]}`, - success: accepted + type: 'yoroi_connect_response/cardano', + success: false, + err: stringifyError(e), }); } - ); - } else if (message.type === 'connector_rpc_request') { - switch (message.function) { + } else if (message.type === 'connector_rpc_request') { + switch (message.function) { case 'sign_tx': try { checkParamCount(1); @@ -1000,6 +1090,55 @@ function handleInjectorConnect(port) { handleError(e); } break; + case 'auth_sign_hex_payload/cardano': + try { + checkParamCount(1); + await withDb(async (db, localStorageApi) => { + await withSelectedWallet( + tabId, + async (wallet, connection) => { + await RustModule.load(); + const signatureHex = await authSignHexPayload({ + appAuthID: connection?.appAuthID, + deriver: wallet, + payloadHex: message.params[0], + }); + rpcResponse({ + ok: signatureHex + }); + }, + db, + localStorageApi, + ) + }); + } catch (e) { + handleError(e); + } + break; + case 'auth_check_hex_payload/cardano': + try { + checkParamCount(2); + await withSelectedSiteConnection(tabId, async connection => { + if (connection?.status?.auth) { + await RustModule.load(); + const [payloadHex, signatureHex] = message.params; + const pk = RustModule.WalletV4.PublicKey + .from_bytes(Buffer.from(String(connection.status?.auth?.pubkey), 'hex')); + const sig = RustModule.WalletV4.Ed25519Signature.from_hex(signatureHex); + const res = pk.verify(Buffer.from(payloadHex, 'hex'), sig); + rpcResponse({ + ok: res + }); + } else { + rpcResponse({ + err: 'auth_check_hex_payload is requested but no auth is present in the connection!', + }); + } + }); + } catch (e) { + handleError(e); + } + break; default: rpcResponse({ err: { diff --git a/packages/yoroi-extension/chrome/extension/ergo-connector/types.js b/packages/yoroi-extension/chrome/extension/ergo-connector/types.js index c9434c8564..9b448b3db7 100644 --- a/packages/yoroi-extension/chrome/extension/ergo-connector/types.js +++ b/packages/yoroi-extension/chrome/extension/ergo-connector/types.js @@ -393,9 +393,27 @@ export type PublicDeriverCache = {| checksum: void | WalletChecksum, |} -export type WhitelistEntry = {| url: string, publicDeriverId: number, image: string |}; +export type WalletAuthEntry = {| + walletId: string, + pubkey: string, +|}; + +export type WhitelistEntry = {| + url: string, + protocol: 'ergo' | 'cardano', + publicDeriverId: number, + appAuthID: ?string, + auth: ?WalletAuthEntry, + image: string, +|}; -export type ConnectingMessage = {| tabId: number, url: string, imgBase64Url: string |}; +export type ConnectingMessage = {| + tabId: number, + url: string, + appAuthID?: string, + imgBase64Url: string, + protocol: 'ergo' | 'cardano', +|}; export type SigningMessage = {| publicDeriverId: number, sign: PendingSignData, @@ -452,6 +470,7 @@ export type ConnectResponseData = {| type: 'connect_response', accepted: true, publicDeriverId: number, + auth: ?WalletAuthEntry, tabId: ?number, |} | {| type: 'connect_response', diff --git a/packages/yoroi-extension/chrome/extension/index.js b/packages/yoroi-extension/chrome/extension/index.js index 1110d18908..e1abf11785 100644 --- a/packages/yoroi-extension/chrome/extension/index.js +++ b/packages/yoroi-extension/chrome/extension/index.js @@ -12,6 +12,7 @@ import { Action } from '../../app/actions/lib/Action'; import App from '../../app/App'; import BigNumber from 'bignumber.js'; import { addCloseListener, TabIdKeys } from '../../app/utils/tabManager'; +import { Logger } from '../../app/utils/logging'; // run MobX in strict mode configure({ enforceActions: 'always' }); @@ -28,6 +29,8 @@ const initializeYoroi: void => Promise = async () => { const history = syncHistoryWithStore(hashHistory, router); const stores = createStores(api, actions, router); + Logger.debug(`[yoroi] stores created`); + window.yoroi = { api, actions, @@ -43,6 +46,8 @@ const initializeYoroi: void => Promise = async () => { if (root == null) { throw new Error('Root element not found.'); } + Logger.debug(`[yoroi] root located`); + render( , root diff --git a/packages/yoroi-extension/features/mock-trezor-connect/index.js b/packages/yoroi-extension/features/mock-trezor-connect/index.js index 417d4f4312..d23888fd3b 100644 --- a/packages/yoroi-extension/features/mock-trezor-connect/index.js +++ b/packages/yoroi-extension/features/mock-trezor-connect/index.js @@ -84,7 +84,7 @@ function deriveAddress( rootKey: RustModule.WalletV4.Bip32PrivateKey, request: CardanoGetAddress, ): RustModule.WalletV4.Address { - if (!request.addressParameters.path) { + if (request.addressParameters.path == null) { throw new Error('unexpected address parameter'); } const spendingKey = derivePath(rootKey, toPath(request.addressParameters.path)); @@ -193,7 +193,7 @@ class MockTrezorConnect { const selectedWallet = MockTrezorConnect.selectedWallet; const address = deriveAddress(selectedWallet.rootKey, request); - if (!request.addressParameters.path) { + if (request.addressParameters.path == null) { throw new Error('unexpected address parameter'); } const arrayPath = toPath(request.addressParameters.path); @@ -435,7 +435,7 @@ class MockTrezorConnect { const withdrawalRequest = request.withdrawals; const withdrawals = RustModule.WalletV4.Withdrawals.new(); for (const withdrawal of withdrawalRequest) { - if (!withdrawal.path) { + if (withdrawal.path == null) { throw new Error('unexpected withdrawal parameter'); } const arrayPath = toPath(withdrawal.path); diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index d1ed1de527..3e069e71ce 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.8.3", + "version": "4.9.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 014d74b4fd..e0976ceb22 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.8.3", + "version": "4.9.2", "description": "Cardano ADA wallet", "scripts": { "dev:build": "rimraf dev/ && babel-node scripts/build --type=debug",