diff --git a/gulpfile.js b/gulpfile.js
index 672c8fe31c188c3a5491cd7f4a9970daf01667da..364e9f9aa947a1cb980dd2c19fe4ca05af6dfef6 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -36,7 +36,7 @@ const gulp = require('gulp'),
   argv = require('yargs').argv,
   sriHash = require('gulp-sri-hash'),
   sort = require('gulp-sort'),
-  gulpfile = this;
+  map = require('map-stream');
 
   // Workaround because @ioni/v1-toolkit use gulp v3.9.2 instead of gulp v4
   let jsonlint;
@@ -62,7 +62,7 @@ const paths = {
   ng_annotate_plugin: ['./www/plugins/*/**/*.js', '!./www/plugins/*/js/vendor/*.js']
 };
 
-const uglifyOptions = {
+const uglifyBaseOptions = {
   toplevel: true,
   warnings: true,
   mangle: {
@@ -80,6 +80,13 @@ const uglifyOptions = {
     max_line_len: 120000
   }
 };
+const debugBaseOptions = {
+  title: 'Processing',
+  minimal: true,
+  showFiles: argv.debug || false,
+  showCount: argv.debug || false,
+  logger: m => log(colors.grey(m))
+};
 
 function appAndPluginWatch(done) {
 
@@ -102,6 +109,15 @@ function appAndPluginWatch(done) {
   if (done) done();
 }
 
+
+function appAndPluginClean() {
+  return del([
+    './www/dist',
+    './www/css/ionic.app*.css',
+    './www/css/leaflet.app*.css'
+  ]);
+}
+
 /**
  * Generate App CSS (using SASS)
  * @returns {*}
@@ -157,8 +173,25 @@ function appConfig() {
     .pipe(gulp.dest('www/js'));
 }
 
+function appAndPluginLint(done) {
+  log(colors.green('Linting JS files...'));
+
+  // Copy Js (and remove unused code)
+  return gulp.src(paths.ng_annotate.concat(paths.ng_annotate_plugin))
+    .pipe(debug({...debugBaseOptions, title: 'Linting'}))
+    .pipe(jshint())
+    .pipe(jshint.reporter(require('jshint-stylish')))
+    .pipe(map( (file, cb) => {
+      if (!file.jshint.success) {
+        console.error('jshint failed');
+        process.exit(1);
+      }
+    }))
+    .on('end', done);
+}
+
 function appNgTemplate() {
-  log(colors.green('Building App template file...'));
+  log(colors.green('Building template file...'));
 
   return gulp.src(paths.templatecache)
     .pipe(sort())
@@ -188,7 +221,7 @@ function appNgAnnotate(event) {
 }
 
 function appNgTranslate() {
-  log(colors.green('Building App translation file...'));
+  log(colors.green('Building translation file...'));
 
   return gulp.src('www/i18n/locale-*.json')
     .pipe(jsonlint())
@@ -281,7 +314,7 @@ function pluginSass() {
   return merge(
 
     // Copy plugins CSS
-    gulp.src(paths.css_plugin, {read: false})
+    gulp.src(paths.css_plugin)
       .pipe(gulp.dest('www/dist/dist_css/plugins')),
 
     // Leaflet images
@@ -481,8 +514,8 @@ function webPluginNgTemplate() {
   return gulp.src(targetPath + '/plugins/**/*.html')
     .pipe(sort())
     .pipe(templateCache({
-      standalone:true,
-      module:"cesium.plugins.templates",
+      standalone: true,
+      module: "cesium.plugins.templates",
       root: "plugins/"
     }))
     .pipe(gulp.dest(targetPath + '/dist/dist_js/plugins'));
@@ -514,7 +547,7 @@ function webUglify() {
 
       // Process JS
       .pipe(jsFilter)
-      .pipe(uglify(uglifyOptions)) // Minify javascript files
+      .pipe(uglify(uglifyBaseOptions)) // Minify javascript files
       .pipe(jsFilter.restore)
 
       // Process CSS
@@ -585,7 +618,7 @@ function webApiUglify() {
 
       // Process JS
       .pipe(jsFilter)
-      .pipe(uglify(uglifyOptions)) // Minify any javascript sources
+      .pipe(uglify(uglifyBaseOptions)) // Minify any javascript sources
       .pipe(jsFilter.restore)
 
       // Process CSS
@@ -856,8 +889,8 @@ function cdvAddPlatformToBodyTag() {
   }
 }
 
-function cdvCopyFiles() {
-  log(colors.green('Copying files... '));
+function cdvNgAnnotate() {
+  log(colors.green('Building JS files... '));
 
   const projectRoot = argv.root || '.';
   const platform = argv.platform || 'android';
@@ -882,17 +915,17 @@ function cdvCopyFiles() {
     gulp.src(wwwPath + '/plugins/**/*.js')
       .pipe(ngAnnotate({single_quotes: true}))
       .pipe(gulp.dest(wwwPath + '/dist/dist_js/plugins')),
-
+/*
     // Copy plugin CSS
-    gulp.src(wwwPath + '/plugins/*/css/**/*.css')
+    gulp.src(wwwPath + '/plugins/!*!/css/!**!/!*.css')
       .pipe(gulp.dest(wwwPath + '/dist/dist_css/plugins')),
 
     // Copy Leaflet images
     pluginLeafletImages(wwwPath + '/img'),
 
     // Copy Leaflet CSS
-    gulp.src('./www/css/**/leaflet.*')
-      .pipe(gulp.dest(wwwPath + '/css'))
+    gulp.src('./www/css/!**!/leaflet.*')
+      .pipe(gulp.dest(wwwPath + '/css'))*/
 
   );
 }
@@ -1099,7 +1132,7 @@ function cdvUglify() {
     const jsLibFilter = filter(['*/lib/**/*.js', '*/js/vendor/**/*.js'], {restore: true}); // External libs only
     const cssFilter = filter("**/*.css", {restore: true});
     const cdvUglifyOptions = {
-      ...uglifyOptions,
+      ...uglifyBaseOptions,
       ecma: '5'
     };
     const debugOptions = {
@@ -1238,11 +1271,12 @@ function cdvAndroidManifest() {
 
   const projectRoot = argv.root || '.';
   const platform = argv.platform || 'android';
+  if (platform !== 'android') return Promise.resolve(); // Skip
 
   const srcMainPath = path.join(projectRoot, 'platforms', platform, 'app', 'src', 'main');
   const androidManifestFile = path.join(srcMainPath, 'AndroidManifest.xml');
 
-  log(colors.green(' Updating Android manifest... ') + colors.grey(androidManifestFile));
+  log(colors.green('Patch Android manifest... ') + colors.grey(androidManifestFile));
 
   if (!fs.existsSync(androidManifestFile)) {
     throw Error("Missing required file " + androidManifestFile);
@@ -1276,6 +1310,8 @@ function cdvAndroidCheckSigning() {
 
   const projectRoot = argv.root || '.';
   const platform = argv.platform || 'android';
+  if (platform !== 'android') return Promise.resolve(); // Skip
+
   const targetPath = path.join(projectRoot, 'platforms', platform);
   const signingFile = path.join(targetPath, 'release-signing.properties');
 
@@ -1288,28 +1324,18 @@ function cdvAndroidCheckSigning() {
   return Promise.resolve();
 }
 
-function cdvAfterPrepare(done, projectRoot, platform) {
+function cdvAsHook(wrapper) {
 
-  projectRoot = (typeof projectRoot === 'string' && projectRoot) || argv.root || '.';
-  platform = ((typeof platform === 'string' && platform) || argv.platform || 'android').toLowerCase();
+  return (done, projectRoot, platform) => {
+    projectRoot = (typeof projectRoot === 'string' && projectRoot) || argv.root || '.';
+    platform = ((typeof platform === 'string' && platform) || argv.platform || 'android').toLowerCase();
 
-  // Override arguments, to pass it to other tasks
-  argv.root = projectRoot;
-  argv.platform = platform;
+    // Override arguments, to pass it to other tasks
+    argv.root = projectRoot;
+    argv.platform = platform;
 
-  let wrapper = gulp.series(
-    gulp.parallel(cdvCopyFiles, cdvAddPlatformToBodyTag),
-    cdvRemoveCode,
-    gulp.parallel(cdvNgTemplate, cdvNgTranslate),
-    cdvUglify,
-    gulp.parallel(cdvCleanUnusedDirectories,cdvCopyBuildFiles)
-  );
-
-  if (platform === 'android') {
-    wrapper = gulp.series(wrapper, cdvAndroidManifest, cdvAndroidCheckSigning);
+    wrapper(done);
   }
-
-  wrapper(done);
 }
 
 function help() {
@@ -1372,15 +1398,6 @@ const webExtBuild = gulp.series(
 );
 
 
-exports.cdvRemoveCode = cdvRemoveCode;
-exports.cdvNgTemplate = cdvNgTemplate;
-exports.cdvNgTranslate = cdvNgTranslate;
-exports.cdvUglify = cdvUglify;
-exports.cdvCleanUnusedDirectories = cdvCleanUnusedDirectories;
-exports.cdvCopyBuildFiles = cdvCopyBuildFiles;
-exports.cdvAndroidManifest = cdvAndroidManifest;
-exports.cdvAndroidCheckSigning = cdvAndroidCheckSigning;
-exports.cdvAfterPrepare = cdvAfterPrepare;
 
 /* --------------------------------------------------------------------------
    -- Define public tasks
@@ -1392,20 +1409,36 @@ exports.license = appLicense;
 exports.sass = appAndPluginSass;
 exports.translate = translate;
 exports.template = template;
-exports.annotate = appNgAnnotate;
+exports.clean = appAndPluginClean;
+exports.lint = appAndPluginLint;
+exports.annotate = gulp.series(appNgAnnotate, pluginNgAnnotate);
 exports.watch = appAndPluginWatch;
 exports.build = build;
 
+// Web
 exports.webCompile = webCompile;
 exports.webBuild = webBuild;
 exports['build:web'] = exports.webBuild; // Alias
 
+// Web extension
 exports.webExtClean = webExtClean;
 exports.webExtCompile = webExtCompile;
 exports.webExtBuild = webExtBuild;
 exports.webExtCopyFiles = webExtCopyFiles;
 exports['build:webExt'] = exports.webBuild; // Alias
 
+// Cordova (hooks)
+const cdvAfterPrepare = gulp.series(
+  gulp.parallel(cdvNgAnnotate, cdvAddPlatformToBodyTag),
+  cdvRemoveCode,
+  gulp.parallel(cdvNgTemplate, cdvNgTranslate),
+  cdvUglify,
+  gulp.parallel(cdvCleanUnusedDirectories, cdvCopyBuildFiles),
+  // Android tasks
+  gulp.parallel(cdvAndroidManifest, cdvAndroidCheckSigning),
+);
+exports.cdvAfterPrepare = cdvAsHook(cdvAfterPrepare);
+
 exports.default = gulp.series(appConfig, build);
 exports.serveBefore = gulp.series(build, appAndPluginWatch);
 exports['ionic:serve:before'] = exports.serveBefore; // Alias need need by @ionic/cli
diff --git a/package.json b/package.json
index d5feb51e5c0b18b36b0fdba14231959ff7af6ecd..96899ea4b7fda974d19650b2fee8dd3e90b4ad87 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
   "scripts": {
     "clean": "trash www/dist/** dist/web/* dist/desktop/**/*.deb platforms/android/**/*.apk",
     "postinstall": "node scripts/node/postinstall.js",
-    "lint": "node scripts/node/jshint.js",
+    "lint": "gulp lint",
     "install-platforms": "ionic cordova prepare",
     "start": "ionic serve",
     "start:webExt": "gulp webExtCompile && web-ext run --source-dir ./dist/web/ext/",
@@ -151,8 +151,10 @@
     "gulp-useref": "^4.0.1",
     "gulp-zip": "^5.0.2",
     "ionic-plugin-keyboard": "^2.2.1",
-    "jshint": "^2.10.3",
+    "jshint": "^2.12.0",
+    "jshint-stylish": "^2.2.1",
     "lazypipe": "^1.0.2",
+    "map-stream": "0.0.7",
     "merge2": "^1.3.0",
     "mv": "^2.1.1",
     "node-sass": "^4.14.1",
diff --git a/scripts/hooks/before_prepare.js b/scripts/hooks/before_prepare.js
index 811e08822faeb44b3b136b9a33e0bd76021c9d44..6384ee6c98221fb610dc998d3dd5e7318c94d33f 100644
--- a/scripts/hooks/before_prepare.js
+++ b/scripts/hooks/before_prepare.js
@@ -1,8 +1,10 @@
 #!/usr/bin/env node
 
-const log = require('fancy-log'),
+const gulp = require('gulp'),
+  path = require("path"),
   colors = require('ansi-colors'),
-  jshint = require('../node/jshint-utils');
+  jshint = require('../node/jshint-utils'),
+  log = require('fancy-log');
 
 module.exports = function(context) {
   const now = Date.now();
@@ -10,10 +12,13 @@ module.exports = function(context) {
 
   const projectRoot = context && context.opts && context.opts.projectRoot || '.';
   const platforms = context && context.opts && context.opts.platforms || ['android'];
-  if (!projectRoot || !platforms) return; // Skip
+
+  const gulpFile = require(path.join(projectRoot, 'gulpfile'));
+
+  if (!projectRoot || !platforms || !gulpFile) return; // Skip
 
   // Run JS Lint
-  return jshint.validate(projectRoot)
+  return jshint.validate(projectRoot) // new Promise(done => gulpFile.lint(done))
     .then(() => {
       log(colors.grey("Hook 'before_prepare' finished in " + (Date.now() - now) + 'ms'));
     });
diff --git a/scss/ionic.app.scss b/scss/ionic.app.scss
index 0a544a5fc507fa65100f8598820e0e656bccee5d..cf11d75726e1c47926a877e8d2b15fa937542a4e 100644
--- a/scss/ionic.app.scss
+++ b/scss/ionic.app.scss
@@ -241,12 +241,6 @@ $screen-menu:                     $screen-sm;
   }
 }
 
-@media screen and (max-width: $screen-sm-max) {
-  body {
-      cursor: url('http://ionicframework.com/img/finger.png'), auto;
-  }
-}
-
 @media screen and (min-width: $screen-md) {
   body {
     cursor: inherit;
diff --git a/www/js/config.js b/www/js/config.js
index 8f3722b07ec6873b302141795194e783db6f5fa1..045782e1d0462df71ddd8264e1174543f9a23bf1 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -120,8 +120,8 @@ angular.module("cesium.config", [])
 		}
 	},
 	"version": "1.6.7",
-	"build": "2020-05-06T09:07:27.415Z",
+	"build": "2020-08-11T08:40:38.369Z",
 	"newIssueUrl": "https://git.duniter.org/clients/cesium-grp/cesium/issues/new"
 })
 
-;
+;
\ No newline at end of file
diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js
index 8fa7643ef9240be2fd2433eb97a0192edf44fe0f..9e3c703f99837fcb7ebb868ff73171a2fe1307db 100644
--- a/www/js/controllers/app-controllers.js
+++ b/www/js/controllers/app-controllers.js
@@ -67,81 +67,6 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $
       .then(UIUtils.loading.hide);
   };
 
-  // removeIf(no-device)
-  ////////////////////////////////////////
-  // Device only methods
-  // (code removed when NO device)
-  ////////////////////////////////////////
-
-  $scope.scanQrCodeAndGo = function() {
-
-    if (!Device.barcode.enable) return;
-
-    // Run scan cordova plugin, on device
-    return Device.barcode.scan()
-      .then(function(data) {
-        if (!data) return;
-
-        // Try to parse as an URI
-        return BMA.uri.parse(data)
-          .then(function(res){
-            if (!res || !res.pubkey) throw {message: 'ERROR.SCAN_UNKNOWN_FORMAT'};
-            // If pubkey: open the identity
-            return $state.go('app.wot_identity', {
-              pubkey: res.pubkey,
-              node: res.host ? res.host: null}
-            );
-          })
-
-          // Not an URI: try WIF or EWIF format
-          .catch(function(err) {
-            console.debug("[app] Scan data is not an URI (get error: " + (err && err.message || err) + "). Trying to decode as a WIF or EWIF format...");
-
-            // Try to read as WIF format
-            return csCrypto.keyfile.parseData(data)
-              .then(function(keypair) {
-                if (!keypair || !keypair.signPk || !keypair.signSk) throw err; // rethrow the first error (e.g. Bad URI)
-
-                var pubkey = CryptoUtils.base58.encode(keypair.signPk);
-                console.debug("[app] Detected WIF/EWIF format. Will login to wallet {" + pubkey.substring(0, 8) + "}");
-
-                // Create a new wallet (if default wallet is already used)
-                var wallet = !csWallet.isLogin() ? csWallet : csWallet.children.create({store: false});
-
-                // Login using keypair
-                return wallet.login({
-                  silent: true,
-                  forceAuth: true,
-                  minData: false,
-                  authData: {
-                    pubkey: pubkey,
-                    keypair: keypair
-                  }
-                })
-                  .then(function () {
-
-                    // Open transfer all wallet
-                    $ionicHistory.nextViewOptions({
-                      historyRoot: true
-                    });
-                    return $state.go('app.new_transfer', {
-                      all: true, // transfer all sources
-                      wallet: !wallet.isDefault() ? wallet.id : undefined
-                    });
-                  });
-              })
-              // Unknown format (nor URI, nor WIF/EWIF)
-              .catch(UIUtils.onError('ERROR.SCAN_UNKNOWN_FORMAT'));
-          });
-      })
-      .catch(UIUtils.onError('ERROR.SCAN_FAILED'));
-  };
-
-  ////////////////////////////////////////
-  // End of device only methods
-  ////////////////////////////////////////
-  // endRemoveIf(no-device)
-
   ////////////////////////////////////////
   // Show Help tour
   ////////////////////////////////////////
@@ -465,6 +390,83 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $
     return false;
   };
 
+  /**
+   * Parse an external URI (see g1lien), and open the expected state
+   * @param uri
+   * @returns {*}
+   */
+  $scope.handleUri = function(uri) {
+    if (!uri) return $q.when(); // Skip
+
+    console.info('[app] Parsing external uri: ', uri);
+    var fromHomeState = $state.current && $state.current.name === 'app.home';
+
+    // Parse the URI
+    return BMA.uri.parse(uri)
+      .then(function(res) {
+        if (!res) throw {message: 'ERROR.UNKNOWN_URI_FORMAT'}; // Continue
+
+        if (res.pubkey) {
+           $state.go('app.wot_identity',
+            angular.merge({
+              pubkey: res.pubkey,
+              action: res.params && res.params.amount ? 'transfer' : undefined
+            }, res.params),
+            {reload: true});
+        }
+        else if (res.uid) {
+          return $state.go('app.wot_identity_uid',
+            angular.merge({
+              uid: res.uid,
+              action: res.params && res.params.amount ? 'transfer' : undefined
+            }, res.params),
+            {reload: true});
+        }
+        else if (angular.isDefined(res.block)) {
+          return $state.go('app.view_block',
+            angular.merge(res.block, res.params),
+            {reload: true});
+        }
+        // Default: wot lookup
+        else {
+          console.warn('[app] TODO implement state redirection from URI result: ', res, uri);
+          return $state.go('app.wot_lookup.tab_search',
+            {q: uri},
+            {reload: true});
+        }
+      })
+
+      // After state change
+      .then(function() {
+        if (fromHomeState) {
+          // Wait 500ms, then remove /app/home?uri from the history
+          // to make sure the back button will work fine
+          return $timeout(function () {
+            if ($ionicHistory.backView()) $ionicHistory.removeBackView();
+          }, 500);
+        }
+      })
+
+      .catch(function(err) {
+        console.error("[home] Error while handle uri {" + uri + "': ", err);
+        return UIUtils.onError(uri)(err);
+      });
+  };
+
+  $scope.registerProtocolHandlers = function() {
+    var protocols = ['web+june', /*'june' - NOT yet accepted by web browser */];
+
+    _.each(protocols, function(protocol) {
+      console.debug("[app] Registering protocol '%s'...".format(protocol));
+      try {
+        navigator.registerProtocolHandler(protocol, "#/app/home?uri=%s", "Cesium");
+      }
+      catch(err) {
+        console.error("[app] Error while registering protocol '%s'".format(protocol), err);
+      }
+    });
+  };
+
   ////////////////////////////////////////
   // Layout Methods
   ////////////////////////////////////////
@@ -505,8 +507,114 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $
     UIUtils.screen.fullscreen.toggleAll();
   };
 
+  // removeIf(no-device)
+  ////////////////////////////////////////
+  // Device only methods
+  // (code removed when NO device)
+  ////////////////////////////////////////
+
+  $scope.scanQrCodeAndGo = function() {
+
+    if (!Device.barcode.enable) return;
+
+    // Run scan cordova plugin, on device
+    return Device.barcode.scan()
+      .then(function(data) {
+        if (!data) return;
+
+        // Try to parse as an URI
+        return BMA.uri.parse(data)
+          .then(function(res){
+            if (!res || !res.pubkey) throw {message: 'ERROR.SCAN_UNKNOWN_FORMAT'};
+            // If pubkey: open the identity
+            return $state.go('app.wot_identity', {
+              pubkey: res.pubkey,
+              node: res.host ? res.host: null}
+            );
+          })
+
+          // Not an URI: try WIF or EWIF format
+          .catch(function(err) {
+            console.debug("[app] Scan data is not an URI (get error: " + (err && err.message || err) + "). Trying to decode as a WIF or EWIF format...");
+
+            // Try to read as WIF format
+            return csCrypto.keyfile.parseData(data)
+              .then(function(keypair) {
+                if (!keypair || !keypair.signPk || !keypair.signSk) throw err; // rethrow the first error (e.g. Bad URI)
+
+                var pubkey = CryptoUtils.base58.encode(keypair.signPk);
+                console.debug("[app] Detected WIF/EWIF format. Will login to wallet {" + pubkey.substring(0, 8) + "}");
+
+                // Create a new wallet (if default wallet is already used)
+                var wallet = !csWallet.isLogin() ? csWallet : csWallet.children.create({store: false});
+
+                // Login using keypair
+                return wallet.login({
+                  silent: true,
+                  forceAuth: true,
+                  minData: false,
+                  authData: {
+                    pubkey: pubkey,
+                    keypair: keypair
+                  }
+                })
+                  .then(function () {
+
+                    // Open transfer all wallet
+                    $ionicHistory.nextViewOptions({
+                      historyRoot: true
+                    });
+                    return $state.go('app.new_transfer', {
+                      all: true, // transfer all sources
+                      wallet: !wallet.isDefault() ? wallet.id : undefined
+                    });
+                  });
+              })
+              // Unknown format (nor URI, nor WIF/EWIF)
+              .catch(UIUtils.onError('ERROR.SCAN_UNKNOWN_FORMAT'));
+          });
+      })
+      .catch(UIUtils.onError('ERROR.SCAN_FAILED'));
+  };
+
+  /**
+   * Process launch intent, as it could have been triggered BEFORE addListeners()
+   * @returns {*}
+   */
+  $scope.processLaunchUri = function() {
+    return Device.intent.last()
+      .then(function(intent) {
+        if (intent) {
+          Device.intent.clear();
+          return $scope.handleUri(intent);
+        }
+      });
+  };
+
+  // Listen for new intent
+  Device.api.intent.on.new($scope, $scope.handleUri);
+  $scope.processLaunchUri();
+
+  ////////////////////////////////////////
+  // End of device only methods
+  ////////////////////////////////////////
+  // endRemoveIf(no-device)
+
+
   // removeIf(device)
+  ////////////////////////////////////////
+  // NOT-Device only methods (web or desktop)
+  // (code removed when build for device - eg. Android, iOS)
+  ////////////////////////////////////////
+
   // Ask switching fullscreen
   $scope.askFullscreen();
+
+  // Register protocol handlers
+  $scope.registerProtocolHandlers();
+
+  ////////////////////////////////////////
+  // End of NOT-device only methods
+  ////////////////////////////////////////
   // endRemoveIf(device)
 }
diff --git a/www/js/controllers/home-controllers.js b/www/js/controllers/home-controllers.js
index a2362a37a3905a861e77842cf52f465affecda29..2632b18f12ab76068a5aa6f8036489e3ab9fd8bf 100644
--- a/www/js/controllers/home-controllers.js
+++ b/www/js/controllers/home-controllers.js
@@ -47,7 +47,7 @@ function HomeController($scope, $state, $timeout, $ionicHistory, $translate, $ht
 
     if (state && state.stateParams && state.stateParams.uri) {
 
-      return csPlatform.uri.open(state.stateParams.uri)
+      return $scope.handleUri(state.stateParams.uri)
         .then(function() {
           $scope.loading = false;
         });
diff --git a/www/js/platform.js b/www/js/platform.js
index 0ef30a900e2448c9f1eb6f5dcf168e0119bb6a3a..640b7e0f9011260d3ea1b766fe7328b7e75b1f02 100644
--- a/www/js/platform.js
+++ b/www/js/platform.js
@@ -227,96 +227,12 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
       return $q.when();
     }
 
-    /**
-     * Parse an external URI (see g1lien), and open the expected state
-     * @param uri
-     * @returns {*}
-     */
-    function openUri(uri) {
-      if (!uri) return $q.when(); // Skip
-
-      console.info('[platform] Detecting external uri: ', uri);
-
-      var parts = csHttp.uri.parse(uri),
-        state, stateParams;
-
-      // G1 lien
-      if (parts.protocol === 'june:') {
-        console.debug("[home] Applying uri...", parts);
-
-        // Pubkey
-        if (parts.hostname && BMA.regexp.PUBKEY.test(parts.hostname)) {
-          state = 'app.wot_identity';
-          stateParams = {pubkey: parts.hostname};
-        } else if ((parts.hostname === 'wallet' || parts.hostname === 'pubkey') && BMA.regexp.PUBKEY.test(parts.pathSegments[0])) {
-          state = 'app.wot_identity';
-          stateParams = {pubkey: parts.pathSegments[0]};
-        }
-
-        // Block
-        else if (parts.hostname === 'block') {
-          state = 'app.view_block';
-          stateParams = {number: parts.pathSegments[0]};
-        }
-
-        // Otherwise, try to a wot lookup
-        else if (parts.hostname && BMA.regexp.USER_ID.test(parts.hostname)) {
-          state = 'app.wot_lookup.tab_search';
-          stateParams = {q: parts.hostname};
-        }
-      }
-
-      if (state === 'app.wot_identity' && parts.searchParams && (angular.isDefined(parts.searchParams.action) || angular.isDefined(parts.searchParams.amount))) {
-        stateParams = angular.merge(stateParams,
-          // Add default actions
-          {action: 'transfer'},
-          // Add path params
-          parts.searchParams);
-      }
-
-      if (state) {
-
-        var fromHomeState = $state.current && $state.current.name === 'app.home';
-
-        // Open the state, after cleaning current location URI
-        return $state.go(state, stateParams, {
-          reload: true
-        })
-          .then(function () {
-            if (fromHomeState) {
-              // This is need to make back button working again
-              return $timeout(function () {
-                if ($ionicHistory.backView()) $ionicHistory.removeBackView();
-              }, 400);
-            }
-          });
-      } else {
-        console.error("[home] Unknown URI format: " + uri);
-        return UIUtils.alert.error("ERROR.UNKNOWN_URI_FORMAT", uri);
-      }
-    }
 
-    function registerProtocolHandlers() {
-      var protocols = ['web+june'/*, 'web+g1', 'g1'*/];
-
-      _.each(protocols, function(protocol) {
-        console.debug("[platform] Registering protocol '%s'...".format(protocol));
-        try {
-          navigator.registerProtocolHandler(protocol, "#/app/home?uri=%s", "Cesium");
-        }
-        catch(err) {
-          console.error("[platform] Error while registering protocol '%s'".format(protocol), err);
-        }
-      });
-    }
 
     function addListeners() {
       listeners = [
         // Listen if node changed
-        BMA.api.node.on.restart($rootScope, restart, this),
-
-        // Listen for new intent
-        Device.api.intent.on.new($rootScope, openUri, this)
+        BMA.api.node.on.restart($rootScope, restart, this)
       ];
     }
 
@@ -345,7 +261,6 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
       // Avoid change state
       disableChangeState();
 
-      registerProtocolHandlers();
 
       // We use 'ionicReady()' instead of '$ionicPlatform.ready()', because this one is callable many times
       startPromise = ionicReady()
@@ -372,7 +287,6 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
         .then(function(){
           enableChangeState();
           addListeners();
-          processLaunchUri();
           startPromise = null;
           started = true;
         })
@@ -403,20 +317,6 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
       }, 500);
     }
 
-    /**
-     * Process launch intent, as it could have been triggered BEFORE addListeners()
-     * @returns {*}
-     */
-    function processLaunchUri() {
-      return Device.intent.last()
-        .then(function(intent) {
-          if (intent) {
-            Device.intent.clear();
-            return openUri(intent);
-          }
-        });
-    }
-
     return  {
       disableChangeState: disableChangeState,
       isStarted: isStarted,
@@ -426,9 +326,6 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
       stop: stop,
       version: {
         latest: getLatestRelease
-      },
-      uri: {
-        open: openUri
       }
     };
   })
diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js
index e045aeb89e01e792f2af0d9e75e32ff6c89efdeb..ffacc98f31e8b2d258929557bdfc0f5e12ebea9f 100644
--- a/www/js/services/bma-services.js
+++ b/www/js/services/bma-services.js
@@ -838,115 +838,78 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
     };
 
     exports.uri.parse = function(uri) {
-      return $q(function(resolve, reject) {
-        var pubkey;
+      if (!uri) return $q.reject("Missing required argument 'uri'");
 
-        // If pubkey: not need to parse
-        if (exact(regexp.PUBKEY).test(uri)) {
+      return $q(function(resolve, reject) {
+        // Pubkey or pubkey+checksum
+        if (exports.regexp.PUBKEY.test(uri) || exports.regexp.PUBKEY_WITH_CHECKSUM.test(uri)) {
           resolve({
             pubkey: uri
           });
         }
-        // If pubkey+checksum
-        else if (exact(regexp.PUBKEY_WITH_CHECKSUM).test(uri)) {
-          console.debug("[BMA.parse] Detecting a pubkey with checksum: " + uri);
-          var matches = exports.regexp.PUBKEY_WITH_CHECKSUM.exec(uri);
-          pubkey = matches[1];
-          var checksum = matches[2];
-          console.debug("[BMA.parse] Detecting a pubkey {"+pubkey+"} with checksum {" + checksum + "}");
-          var expectedChecksum = csCrypto.util.pkChecksum(pubkey);
-          console.debug("[BMA.parse] Expecting checksum for pubkey is {" + expectedChecksum + "}");
-          if (checksum != expectedChecksum) {
-            reject( {message: 'ERROR.PUBKEY_INVALID_CHECKSUM'});
-          }
-          else {
-            resolve({
-              pubkey: pubkey
-            });
-          }
+
+        // Uid
+        else if (uri.startsWith('@') && exports.regexp.USER_ID.test(uid.substr(1))) {
+          resolve({
+            uid: uid.substr(1)
+          });
         }
-        else if(uri.startsWith('duniter://')) {
-          var parser = csHttp.uri.parse(uri),
-            uid,
-            currency = parser.host.indexOf('.') === -1 ? parser.host : null,
-            host = parser.host.indexOf('.') !== -1 ? parser.host : null;
-          if (parser.username) {
-            if (parser.password) {
-              uid = parser.username;
-              pubkey = parser.password;
+
+        // G1 protocols
+        else if(uri.startsWith('june:') || uri.startsWith('web+june:')) {
+          var parser = csHttp.uri.parse(uri);
+
+          // Pubkey (explicit path)
+          var pubkey;
+          if (parser.hostname === 'wallet' || parser.hostname === 'pubkey') {
+            if (exports.regexp.PUBKEY.test(parser.pathSegments[0]) || exports.regexp.PUBKEY_WITH_CHECKSUM.test(parser.pathSegments[0])) {
+              pubkey = parser.pathSegments[0];
+              parser.pathSegments = parser.pathSegments.slice(1);
             }
             else {
-              pubkey = parser.username;
+              reject({message: 'ERROR.INVALID_PUBKEY'});
+              return;
             }
           }
-          if (parser.pathname) {
-            var paths = parser.pathname.split('/');
-            var pathCount = !paths ? 0 : paths.length;
-            var index = 0;
-            if (!currency && pathCount > index) {
-              currency = paths[index++];
-            }
-            if (!pubkey && pathCount > index) {
-              pubkey = paths[index++];
-            }
-            if (!uid && pathCount > index) {
-              uid = paths[index++];
-            }
-            if (pathCount > index) {
-              reject( {message: 'Bad Duniter URI format. Invalid path (incomplete or redundant): '+ parser.pathname}); return;
-            }
+          else if (parser.hostname &&
+            (exports.regexp.PUBKEY.test(parser.hostname) || exports.regexp.PUBKEY_WITH_CHECKSUM.test(parser.hostname))) {
+            pubkey = parser.hostname;
           }
 
-          if (!currency){
-            if (host) {
-              csHttp.get(host + '/blockchain/parameters')()
-              .then(function(parameters){
-                resolve({
-                  uid: uid,
-                  pubkey: pubkey,
-                  host: host,
-                  currency: parameters.currency
-                });
-              })
-              .catch(function(err) {
-                console.error(err);
-                reject({message: 'Could not get node parameter. Currency could not be retrieve'});
-              });
-            }
-            else {
-              reject({message: 'Bad Duniter URI format. Missing currency name (or node address).'}); return;
-            }
+          if (pubkey) {
+            resolve({
+              pubkey: pubkey,
+              pathSegments: parser.pathSegments,
+              params: parser.searchParams
+            });
           }
-          else {
-            if (!host) {
-              resolve({
-                uid: uid,
-                pubkey: pubkey,
-                currency: currency
-              });
-            }
 
-            // Check if currency are the same (between node and uri)
-            return csHttp.get(host + '/blockchain/parameters')()
-              .then(function(parameters){
-                if (parameters.currency !== currency) {
-                  reject( {message: "Node's currency ["+parameters.currency+"] does not matched URI's currency ["+currency+"]."}); return;
-                }
-                resolve({
-                  uid: uid,
-                  pubkey: pubkey,
-                  host: host,
-                  currency: currency
-                });
-              })
-              .catch(function(err) {
-                console.error(err);
-                reject({message: 'Could not get node parameter. Currency could not be retrieve'});
-              });
+          // UID
+          else if (parser.hostname && parser.hostname.startsWith('@') && exports.regexp.USER_ID.test(parser.hostname.substr(1))) {
+            resolve({
+              uid: parser.hostname.substr(1),
+              pathSegments: parser.pathSegments,
+              params: parser.searchParams
+            });
+          }
+
+          // Block
+          else if (parser.hostname === 'block') {
+            resolve({
+              block: {number: parser.pathSegments[0]},
+              pathSegments: parser.pathSegments.slice(1),
+              params: parser.searchParams
+            });
+          }
+
+          // Other case
+          else {
+            console.debug("[BMA.parse] Unknown URI format: " + uri);
+            reject({message: 'ERROR.UNKNOWN_URI_FORMAT'});
           }
         }
         else {
-          console.debug("[BMA.parse] Could not parse URI: " + uri);
+          console.debug("[BMA.parse] Unknown URI format: " + uri);
           reject({message: 'ERROR.UNKNOWN_URI_FORMAT'});
         }
       })
@@ -954,14 +917,20 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
       // Check values against regex
       .then(function(result) {
         if (!result) return;
-        if (result.pubkey && !(exact(regexp.PUBKEY).test(result.pubkey))) {
-          throw {message: "Invalid pubkey format [" + result.pubkey + "]"};
-        }
-        if (result.uid && !(exact(regexp.USER_ID).test(result.uid))) {
-          throw {message: "Invalid uid format [" + result.uid + "]"};
-        }
-        if (result.currency && !(exact(regexp.CURRENCY).test(result.currency))) {
-          throw {message: "Invalid currency format ["+result.currency+"]"};
+
+        // Validate checksum
+        if (result.pubkey && exports.regexp.PUBKEY_WITH_CHECKSUM.test(result.pubkey)) {
+          console.debug("[BMA.parse] Validating pubkey checksum... ");
+          var matches = exports.regexp.PUBKEY_WITH_CHECKSUM.exec(uri);
+          pubkey = matches[1];
+          var checksum = matches[2];
+          var expectedChecksum = csCrypto.util.pkChecksum(pubkey);
+          if (checksum !== expectedChecksum) {
+            console.warn("[BMA.parse] Detecting a pubkey {"+pubkey+"} with checksum {" + checksum + "}, but expecting checksum is {" + expectedChecksum + "}");
+            throw {message: 'ERROR.PUBKEY_INVALID_CHECKSUM'};
+          }
+          result.pubkey = pubkey;
+          result.pubkeyChecksum = checksum;
         }
         return result;
       });
diff --git a/www/js/services/device-services.js b/www/js/services/device-services.js
index 5479c3b1988c615fd100e8997893e8b8078644d6..a756a437d8d7cebc61b62fe65b6cd09aa61dc729 100644
--- a/www/js/services/device-services.js
+++ b/www/js/services/device-services.js
@@ -153,27 +153,25 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
         return deferred.promise;
       }
 
-      function handleOpenUri(intent) {
-        if (intent) {
-          console.info('[platform] Received new intent: ', intent);
-          cache.lastIntent = intent; // Remember, for last()
-          api.intent.raise.new(intent);
-        }
+    // WARN: Need by cordova-plugin-customurlscheme
+    window.handleOpenURL = function(intent) {
+      if (intent) {
+        console.info('[device] Received new intent: ', intent);
+        cache.lastIntent = intent; // Remember, for last()
+        api.intent.raise.new(intent);
       }
+    };
 
-      exports.intent = {
+    exports.intent = {
         enable: false,
         last: function() {
           return $q.when(cache.lastIntent);
         },
-        handle: handleOpenUri,
         clear: function() {
           cache.lastIntent = undefined;
         }
       };
 
-      window.handleOpenURL = handleOpenUri; // Need by cordova-plugin-customurlscheme
-
       // Numerical keyboard - fix #30
       exports.keyboard.digit = {
         settings: {
diff --git a/yarn.lock b/yarn.lock
index f0b798ae545cf7ac3d04b0e6015128f3478a9d5c..1cdf837d80719716bd4609c571b62b2825cbc10b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5747,6 +5747,11 @@ ipaddr.js@1.9.1:
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
   integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
+irregular-plurals@^1.0.0:
+  version "1.4.0"
+  resolved "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766"
+  integrity sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y=
+
 irregular-plurals@^2.0.0:
   version "2.0.0"
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/irregular-plurals/-/irregular-plurals-2.0.0.tgz#39d40f05b00f656d0b7fa471230dd3b714af2872"
@@ -6214,9 +6219,21 @@ jsesc@2.5.2:
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
   integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
 
-jshint@^2.10.3:
+jshint-stylish@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.npmjs.org/jshint-stylish/-/jshint-stylish-2.2.1.tgz#242082a2c035ae03fd81044e0570cc4208cf6e61"
+  integrity sha1-JCCCosA1rgP9gQROBXDMQgjPbmE=
+  dependencies:
+    beeper "^1.1.0"
+    chalk "^1.0.0"
+    log-symbols "^1.0.0"
+    plur "^2.1.0"
+    string-length "^1.0.0"
+    text-table "^0.2.0"
+
+jshint@^2.12.0:
   version "2.12.0"
-  resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/jshint/-/jshint-2.12.0.tgz#52e75bd058d587ef81a0e2f95e5cf18eb5dc5c37"
+  resolved "https://registry.npmjs.org/jshint/-/jshint-2.12.0.tgz#52e75bd058d587ef81a0e2f95e5cf18eb5dc5c37"
   integrity sha512-TwuuaUDmra0JMkuqvqy+WGo2xGHSNjv1BA1nTIgtH2K5z1jHuAEeAgp7laaR+hLRmajRjcrM71+vByBDanCyYA==
   dependencies:
     cli "~1.0.0"
@@ -7024,6 +7041,13 @@ lodash@^4.0.0, lodash@^4.12.0, lodash@^4.14.0, lodash@^4.14.1, lodash@^4.15.0, l
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
   integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
 
+log-symbols@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
+  integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
+  dependencies:
+    chalk "^1.0.0"
+
 log-update@^4.0.0:
   version "4.0.0"
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
@@ -8512,6 +8536,13 @@ plugin-log@^0.1.0:
     chalk "^1.1.1"
     dateformat "^1.0.11"
 
+plur@^2.1.0:
+  version "2.1.2"
+  resolved "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a"
+  integrity sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=
+  dependencies:
+    irregular-plurals "^1.0.0"
+
 plur@^3.0.0:
   version "3.1.1"
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/plur/-/plur-3.1.1.tgz#60267967866a8d811504fe58f2faaba237546a5b"
@@ -9975,6 +10006,13 @@ streamfilter@^3.0.0:
   dependencies:
     readable-stream "^3.0.6"
 
+string-length@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac"
+  integrity sha1-VpcPscOFWOnnC3KL894mmsRa36w=
+  dependencies:
+    strip-ansi "^3.0.0"
+
 string-template@~0.2.0, string-template@~0.2.1:
   version "0.2.1"
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"