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 @@
-
-
+
+
+ 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",