diff --git a/src/assets/images/device_entry.svg b/src/assets/images/device_entry.svg
new file mode 100755
index 0000000000000000000000000000000000000000..d8c50723485e125d65e597a51435e64f08d71ef8
--- /dev/null
+++ b/src/assets/images/device_entry.svg
@@ -0,0 +1,17 @@
+<svg width="94" height="94" viewBox="0 0 94 94" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g fill="none">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M 45 90C 69.8528 90 90 69.8528 90 45C 90 20.1472 69.8528 0 45 0C 20.1472 0 0 20.1472 0 45C 0 69.8528 20.1472 90 45 90Z" transform="translate(2 2)" fill="#D8D8D8" fill-opacity="0.01"/>
+<path d="M 45 90C 69.8528 90 90 69.8528 90 45C 90 20.1472 69.8528 0 45 0C 20.1472 0 0 20.1472 0 45C 0 69.8528 20.1472 90 45 90Z" transform="translate(2 2)" stroke="white" stroke-width="3"/>
+<path d="M 6.43085 10.78C 4.63879 11.0707 0.989932 11.8268 0 12.3286C 0.407286 0.547339 6.88406 -1.40044 10.4308 0.779999C 6.20865 1.90137 6.32903 8.23614 6.43085 10.78Z" transform="translate(65.9308 28.22) scale(-1 1)" fill="#C4C4C4"/>
+<path d="M 6.43085 10.78C 4.63879 11.0707 0.989932 11.8268 0 12.3286C 0.407286 0.547339 6.88406 -1.40044 10.4308 0.779999C 6.20865 1.90137 6.32903 8.23614 6.43085 10.78Z" transform="translate(65.9308 28.22) scale(-1 1)" fill="#C4C4C4"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M 24.5605 5.80482C 25.9101 5.97699 27.2066 5.21939 27.7191 3.95909C 28.2562 2.6386 28.7836 1.47353 29.2965 0.449768C 30.5023 0.486954 31.5953 0.526215 32.5396 0.576248C 37.6468 0.846878 41.0005 2.3575 43.082 4.39674C 41.652 5.02917 39.9324 5.87749 38.2806 6.7238C 36.5808 7.59465 34.9323 8.47398 33.7101 9.1348C 33.0986 9.46544 32.5928 9.74187 32.2397 9.93584C 32.063 10.0328 31.9245 10.1093 31.8299 10.1615L 31.6837 10.2425C 31.1482 10.5401 30.8475 11.1333 30.924 11.741L 30.9331 20.2067C 30.9927 20.6802 31.274 21.097 31.6908 21.3294C 32.1127 21.5701 32.6088 21.5076 33.0902 21.3629L 33.2202 21.3031C 33.3336 21.2509 33.4994 21.1743 33.71 21.0762C 33.9433 20.9677 34.2318 20.8327 34.5654 20.6754C 34.8342 20.5487 35.1323 20.4074 35.4543 20.2536L 35.6971 20.1377C 37.2061 19.4175 39.9301 18.1175 41.7683 17.163C 42.9724 16.5377 44.1934 15.8805 45.311 15.2375C 44.9258 16.2405 44.4028 17.0819 43.8096 17.6619L 26.5396 27.0762C 25.1221 26.0333 25.607 25.4727 26.4716 24.473C 28.0096 22.6948 30.7492 19.5272 26.1215 9.78375C 20.5023 5.35844 7.8877 5.27588 2.50793 5.24068C 0.768799 5.22929 -0.214233 5.22285 0.0396118 5.07625C 1.75507 4.08553 12.9818 0.917801 16.6978 0C 16.611 0.1875 16.5275 0.367569 16.4472 0.539215C 16.4064 0.626404 16.3665 0.711426 16.3274 0.794144C 15.9169 1.6626 15.947 2.67513 16.4086 3.51761C 16.8701 4.36008 17.7072 4.93059 18.66 5.05214L 24.5605 5.80482Z" transform="translate(24.9604 37.9238)" fill="white" stroke="white"/>
+<path d="M 22.1005 13L 16.2 13.7527C 8.38013 -5.47592 0.509107 1.80677 0 14.3013C 0.0509108 6.10544 2.69312 -0.0498744 10.3976 0.000304595C 16.5612 0.0404478 19.9736 8.50061 22.1005 13Z" transform="translate(66.1005 27) scale(-1 1)" fill="white"/>
+<mask id="path-7-inside-1" fill="white">
+<path d="M 17.0381 1.11457C 4.27651 2.70692 0.865483 1.71672 0 14.3953C 0 28.1778 7.50085 23.8457 19.1594 21.989C 27.984 20.099 33.092 20.952 33.092 8.25672C 32.2435 -2.02998 29.087 -0.388842 17.0381 1.11457Z"/>
+</mask>
+<g mask="url(#path-7-inside-1)" transform="translate(55.092 41.8761) scale(-1 1)">
+<path d="M 0 14.3953L -3.99071 14.1229L -4 14.2589L -4 14.3953L 0 14.3953ZM 17.0381 1.11457L 16.5429 -2.85465L 16.5429 -2.85465L 17.0381 1.11457ZM 33.092 8.25672L 37.092 8.25672L 37.092 8.09203L 37.0785 7.92789L 33.092 8.25672ZM 19.1594 21.989L 19.7885 25.9393L 19.8933 25.9226L 19.9972 25.9003L 19.1594 21.989ZM 3.99071 14.6677C 4.42662 8.28204 5.48265 7.40822 6.11859 7.02601C 6.69787 6.67785 7.69892 6.34501 9.72536 6.03151C 11.7961 5.71115 14.1328 5.50811 17.5334 5.08379L 16.5429 -2.85465C 13.5627 -2.48279 10.6873 -2.21248 8.50229 -1.87445C 6.27291 -1.52955 3.9743 -1.01897 1.99744 0.16918C -2.47892 2.8596 -3.56114 7.82996 -3.99071 14.1229L 3.99071 14.6677ZM 17.5334 5.08379C 20.749 4.68257 22.9441 4.3304 24.9166 4.12903C 26.8922 3.92733 27.7656 3.9957 28.164 4.1098C 28.1994 4.11993 28.1688 4.01552 28.3076 4.30732C 28.5839 4.88851 28.9009 6.10498 29.1055 8.58555L 37.0785 7.92789C 36.8588 5.2651 36.4631 2.8292 35.5324 0.87196C 34.4642 -1.37468 32.7323 -2.9036 30.3664 -3.58109C 28.3635 -4.15462 26.1356 -4.037 24.1041 -3.8296C 22.0695 -3.62189 19.3518 -3.20513 16.5429 -2.85465L 17.5334 5.08379ZM 29.092 8.25672C 29.092 14.2794 27.8449 15.4212 27.1938 15.8616C 26.6416 16.2352 25.7755 16.5727 24.1872 16.9275C 23.4117 17.1007 22.5628 17.2587 21.563 17.444C 20.5854 17.6251 19.4873 17.8281 18.3217 18.0778L 19.9972 25.9003C 21.0377 25.6775 22.0296 25.4937 23.0204 25.3101C 23.989 25.1306 24.9869 24.946 25.9313 24.7351C 27.7826 24.3215 29.8482 23.7244 31.676 22.4881C 35.785 19.7087 37.092 14.9293 37.092 8.25672L 29.092 8.25672ZM 18.5304 18.0388C 15.5477 18.5138 12.619 19.1856 10.4897 19.5963C 8.12938 20.0516 6.6645 20.2052 5.69349 20.0864C 5.01742 20.0037 4.93914 19.8623 4.80067 19.6272C 4.46511 19.0575 4 17.6196 4 14.3953L -4 14.3953C -4 18.0622 -3.5275 21.2512 -2.09232 23.6876C -0.460049 26.4586 2.06554 27.7022 4.72201 28.0272C 7.08353 28.3161 9.67984 27.9 12.0049 27.4516C 14.561 26.9585 16.9419 26.3926 19.7885 25.9393L 18.5304 18.0388Z" fill="white"/>
+</g>
+<path d="M 0 10.3292L 0.685179 4.47255C 4.92876 3.04264 9.69318 1.20359 12.5676 0.221048C 14.5183 -0.445764 15.9365 0.435362 16.0866 2.33438C 16.1919 3.66552 15.1923 4.93186 13.973 5.41957C 12.7537 5.90728 0 10.3292 0 10.3292Z" transform="matrix(0.993226 -0.1162 0.1162 0.993226 57.4919 45.7408)" fill="white"/>
+</g>
+</svg>
diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json
index 7dcd32f8f81d394ed06aa11ee117823a721b0f1f..27c3ffefade88834db8df5692a8316217ae21264 100644
--- a/src/assets/translations.data.json
+++ b/src/assets/translations.data.json
@@ -7,6 +7,10 @@
     "entry.generic-medium": "VR",
     "entry.gearvr-prefix": "Enter on ",
     "entry.gearvr-medium": "GearVR",
+    "entry.device-prefix": "Enter on ",
+    "entry.device-medium": "Device",
+    "entry.device-subtitle-desktop": "Link to Headset or Phone",
+    "entry.device-subtitle-mobile": "Link to Headset or PC",
     "entry.cardboard": "Enter on Google Cardboard",
     "entry.daydream-prefix": "Enter on ",
     "entry.daydream-medium": "Daydream",
diff --git a/src/hub.js b/src/hub.js
index c1d9883e8036838c7d54362b63bb2e61fe0c1a34..2d3ea5d9cee82852d2fa4621541c5d5f1571e130 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -63,8 +63,8 @@ import ReactDOM from "react-dom";
 import React from "react";
 import UIRoot from "./react-components/ui-root";
 import HubChannel from "./utils/hub-channel";
-import XferChannel from "./utils/xfer-channel";
-import { connectToPhoenix } from "./utils/phoenix-utils";
+import LinkChannel from "./utils/link-channel";
+import { connectToReticulum } from "./utils/phoenix-utils";
 
 import "./systems/personal-space-bubble";
 import "./systems/app-mode";
@@ -165,7 +165,7 @@ function mountUI(scene, props = {}) {
 const onReady = async () => {
   const scene = document.querySelector("a-scene");
   const hubChannel = new HubChannel(store);
-  const xferChannel = new XferChannel(store);
+  const linkChannel = new LinkChannel(store);
 
   document.querySelector("a-scene canvas").classList.add("blurred");
   window.APP.scene = scene;
@@ -361,7 +361,7 @@ const onReady = async () => {
   const hubId = qs.hub_id || document.location.pathname.substring(1).split("/")[0];
   console.log(`Hub ID: ${hubId}`);
 
-  const socket = connectToPhoenix();
+  const socket = connectToReticulum();
   const channel = socket.channel(`hub:${hubId}`, {});
 
   channel
@@ -383,7 +383,7 @@ const onReady = async () => {
       console.error(res);
     });
 
-  xferChannel.setSocket(socket);
+  linkChannel.setSocket(socket);
 };
 
 document.addEventListener("DOMContentLoaded", onReady);
diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js
index 92d0ef5ef8ccd4992d9913bfb30144fff018c0b9..04b8624f9da9e7f9e713431aea7759565ce06cfb 100644
--- a/src/react-components/entry-buttons.js
+++ b/src/react-components/entry-buttons.js
@@ -7,7 +7,8 @@ import MobileScreenEntryImg from "../assets/images/mobile_screen_entry.svg";
 import DesktopScreenEntryImg from "../assets/images/desktop_screen_entry.svg";
 import GenericVREntryImg from "../assets/images/generic_vr_entry.svg";
 import GearVREntryImg from "../assets/images/gearvr_entry.svg";
-import DaydreamEntyImg from "../assets/images/daydream_entry.svg";
+import DaydreamEntryImg from "../assets/images/daydream_entry.svg";
+import DeviceEntryImg from "../assets/images/device_entry.svg";
 
 const mobiledetect = new MobileDetect(navigator.userAgent);
 
@@ -22,7 +23,11 @@ const EntryButton = props => (
         <span className="entry-button--bolded">
           <FormattedMessage id={props.mediumMessageId} />
         </span>
-        {props.subtitle && <div className="entry-button__subtitle">{props.subtitle}</div>}
+        {props.subtitle && (
+          <div className="entry-button__subtitle">
+            <FormattedMessage id={props.subtitle} />
+          </div>
+        )}
       </div>
     </div>
   </button>
@@ -72,10 +77,22 @@ export const GearVREntryButton = props => {
 export const DaydreamEntryButton = props => {
   const entryButtonProps = {
     ...props,
-    iconSrc: DaydreamEntyImg,
+    iconSrc: DaydreamEntryImg,
     prefixMessageId: "entry.daydream-prefix",
     mediumMessageId: "entry.daydream-medium"
   };
 
   return <EntryButton {...entryButtonProps} />;
 };
+
+export const DeviceEntryButton = props => {
+  const entryButtonProps = {
+    ...props,
+    iconSrc: DeviceEntryImg,
+    prefixMessageId: "entry.device-prefix",
+    mediumMessageId: "entry.device-medium",
+    subtitle: mobiledetect.mobile() ? "entry.device-subtitle-mobile" : "entry.device-subtitle-desktop"
+  };
+
+  return <EntryButton {...entryButtonProps} />;
+};
diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js
index ab7bdda1c22098e1991afd98ff23ffdeb63df83e..a502fe83affefab13912d59220704bf47016a9b5 100644
--- a/src/react-components/info-dialog.js
+++ b/src/react-components/info-dialog.js
@@ -4,6 +4,7 @@ import classNames from "classnames";
 import PropTypes from "prop-types";
 import { FormattedMessage } from "react-intl";
 import formurlencoded from "form-urlencoded";
+import LinkDialog from "./link-dialog.js";
 
 // TODO i18n
 
@@ -14,12 +15,15 @@ class InfoDialog extends Component {
     invite: Symbol("invite"),
     updates: Symbol("updates"),
     report: Symbol("report"),
-    help: Symbol("help")
+    help: Symbol("help"),
+    link: Symbol("link")
   };
   static propTypes = {
     dialogType: PropTypes.oneOf(Object.values(InfoDialog.dialogTypes)),
     onCloseDialog: PropTypes.func,
-    onSubmittedEmail: PropTypes.func
+    onSubmittedEmail: PropTypes.func,
+    linkChannel: PropTypes.object,
+    onLinkCodeUsed: PropTypes.func
   };
 
   constructor(props) {
@@ -214,6 +218,10 @@ class InfoDialog extends Component {
           </div>
         );
         break;
+      case InfoDialog.dialogTypes.link:
+        dialogTitle = "Enter on Device";
+        dialogBody = <LinkDialog linkChannel={this.props.linkChannel} onLinkCodeUsed={this.props.onLinkCodeUseds} />;
+        break;
     }
 
     const dialogClasses = classNames({
diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js
new file mode 100644
index 0000000000000000000000000000000000000000..e12aa237a6363e8eeb9cebb97b6d76d1c5fc7660
--- /dev/null
+++ b/src/react-components/link-dialog.js
@@ -0,0 +1,21 @@
+import React, { Component } from "react";
+import classNames from "classnames";
+import PropTypes from "prop-types";
+import { FormattedMessage } from "react-intl";
+
+class LinkDialog extends Component {
+  state = {
+    code: null
+  };
+
+  static propTypes = {
+    linkChannel: PropTypes.object,
+    onLinkCodeUsed: PropTypes.func
+  };
+
+  render() {
+    return <div>Hello</div>;
+  }
+}
+
+export default LinkDialog;
diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js
index 82c1c911231ed42bd8853895c6f17a147179351f..8ec0f4a99e2c57225578b8572182096f4cf5cc8d 100644
--- a/src/react-components/ui-root.js
+++ b/src/react-components/ui-root.js
@@ -11,7 +11,13 @@ import screenfull from "screenfull";
 
 import { lang, messages } from "../utils/i18n";
 import AutoExitWarning from "./auto-exit-warning";
-import { TwoDEntryButton, GenericEntryButton, GearVREntryButton, DaydreamEntryButton } from "./entry-buttons.js";
+import {
+  TwoDEntryButton,
+  DeviceEntryButton,
+  GenericEntryButton,
+  GearVREntryButton,
+  DaydreamEntryButton
+} from "./entry-buttons.js";
 import { ProfileInfoHeader } from "./profile-info-header.js";
 import ProfileEntryPanel from "./profile-entry-panel";
 import InfoDialog from "./info-dialog.js";
@@ -588,8 +594,6 @@ class UIRoot extends Component {
       );
     }
 
-    const daydreamMaybeSubtitle = messages["entry.daydream-via-chrome"];
-
     // Only show this in desktop firefox since other browsers/platforms will ignore the "screen" media constraint and
     // will attempt to share your webcam instead!
     const screenSharingCheckbox = this.props.enableScreenSharing &&
@@ -614,6 +618,7 @@ class UIRoot extends Component {
             {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && (
               <GenericEntryButton onClick={this.enterVR} />
             )}
+            <DeviceEntryButton onClick={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.link })} />
             {this.props.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && (
               <GearVREntryButton onClick={this.enterGearVR} />
             )}
@@ -621,7 +626,9 @@ class UIRoot extends Component {
               <DaydreamEntryButton
                 onClick={this.enterDaydream}
                 subtitle={
-                  this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : ""
+                  this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe
+                    ? "entry.daydream-via-chrome"
+                    : null
                 }
               />
             )}
diff --git a/src/utils/xfer-channel.js b/src/utils/link-channel.js
similarity index 78%
rename from src/utils/xfer-channel.js
rename to src/utils/link-channel.js
index e2fbfd5d67760a52fc22c8475121eb277624da2b..418b6d73800289f946624dd61e6b08a83d31d7d8 100644
--- a/src/utils/xfer-channel.js
+++ b/src/utils/link-channel.js
@@ -1,6 +1,6 @@
 import { generatePublicKeyAndEncryptedObject, generateKeys, decryptObject } from "./crypto";
 
-export default class XferChannel {
+export default class LinkChannel {
   constructor(store) {
     this.store = store;
   }
@@ -11,7 +11,7 @@ export default class XferChannel {
 
   // Returns a promise that, when resolved, will forward an object with three keys:
   //
-  // code: The code that was made available to use for xfer.
+  // code: The code that was made available to use for link.
   //
   // cancel: A function that the caller can call to cancel the use of the code.
   //
@@ -26,13 +26,13 @@ export default class XferChannel {
             .toString()
             .padStart(4, "0");
 
-          // Only respond to one xfer_request in this channel.
+          // Only respond to one link_request in this channel.
           let readyToSend = false;
 
-          const channel = this.socket.channel(`xfer:${code}`, { timeout: 10000 });
+          const channel = this.socket.channel(`link:${code}`, { timeout: 10000 });
           const cancel = () => channel.leave();
 
-          channel.on("xfer_expired", () => finished("expired"));
+          channel.on("link_expired", () => finished("expired"));
 
           channel.on("presence_state", state => {
             if (readyToSend) return;
@@ -46,11 +46,11 @@ export default class XferChannel {
             }
           });
 
-          channel.on("xfer_request", incoming => {
+          channel.on("link_request", incoming => {
             if (readyToSend) {
               const data = { path: location.pathname };
 
-              // Copy profile data to xfer'ed device if it's been set.
+              // Copy profile data to link'ed device if it's been set.
               if (this.store.state.activity.hasChangedName) {
                 data.profile = { ...this.store.state.profile };
               }
@@ -63,7 +63,7 @@ export default class XferChannel {
                     data: encryptedData
                   };
 
-                  channel.push("xfer_response", payload);
+                  channel.push("link_response", payload);
                   channel.leave();
                   finished("used");
                   readyToSend = false;
@@ -80,13 +80,13 @@ export default class XferChannel {
     });
   };
 
-  // Attempts to receive an xfer payload from a remote device using the given code.
+  // Attempts to receive an link payload from a remote device using the given code.
   //
   // Promise rejects if the code is invalid or there is a problem with the channel.
-  // Promise resolves and passes payload of xfer source on successful xfer.
-  attemptXfer = code => {
+  // Promise resolves and passes payload of link source on successful link.
+  attemptLink = code => {
     return new Promise((resolve, reject) => {
-      const channel = this.socket.channel(`xfer:${code}`, { timeout: 10000 });
+      const channel = this.socket.channel(`link:${code}`, { timeout: 10000 });
       let finished = false;
 
       generateKeys().then(({ publicKeyString, privateKey }) => {
@@ -94,8 +94,8 @@ export default class XferChannel {
           const numOccupants = Object.keys(state).length;
 
           if (numOccupants === 1) {
-            // Great, only sender is in topic, request xfer
-            channel.push("xfer_request", {
+            // Great, only sender is in topic, request link
+            channel.push("link_request", {
               reply_to_session_id: this.socket.params.session_id,
               public_key: publicKeyString
             });
@@ -109,12 +109,12 @@ export default class XferChannel {
             // Nobody in this channel, probably a bad code.
             reject("failed");
           } else {
-            console.warn("xfer code channel already has 2 or more occupants, something fishy is going on.");
+            console.warn("link code channel already has 2 or more occupants, something fishy is going on.");
             reject("in_use");
           }
         });
 
-        channel.on("xfer_response", payload => {
+        channel.on("link_response", payload => {
           finished = true;
           channel.leave();
 
diff --git a/src/utils/phoenix-utils.js b/src/utils/phoenix-utils.js
index 4cb50474b84ed0322797d7ef3df867d0acbda2dd..3c20f8db96aa261e4d8dd27d727b89b499728e53 100644
--- a/src/utils/phoenix-utils.js
+++ b/src/utils/phoenix-utils.js
@@ -2,7 +2,7 @@ import queryString from "query-string";
 import uuid from "uuid/v4";
 import { Socket } from "phoenix";
 
-export function connectToPhoenix() {
+export function connectToReticulum() {
   const qs = queryString.parse(location.search);
 
   const socketProtocol = qs.phx_protocol || (document.location.protocol === "https:" ? "wss:" : "ws:");