Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/extension popup #82

Merged
merged 5 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,67 @@ to approve or reject the session:
Upon receiving a `session_request` event, process the request. For instance, if the dApp
requests a transaction to be signed:

#### Extension popup

By default, it is not possible to directly pop up an extension with Wallet Connect. However, to
allow this possibility, the dAppConnector look for extensions. If you create the AppConnector,
it will automatically send a message to the extension to detect if it is installed. In case the
extension is installed, it will be added to the available extensions and its data can be found
at the extensions property of dAppConnector.

To connect an available extension, use the method `connectExtension(<extensionId>)`. This will
link the extension to the signer and session. Whenever you use the signer created for this
session, the extension will automatically open. You can find out if the extension is available
by checking the `extensions` property.

```javascript
const dAppConnector = new DAppConnector(
dAppMetadata,
LedgerId.TESTNET,
projectId,
Object.values(HederaJsonRpcMethod),
[HederaSessionEvent.ChainChanged, HederaSessionEvent.AccountsChanged],
[HederaChainId.Testnet]
)

[...]

dAppConnector?.extensions?.forEach((extension) => {
console.log(extension)
})

const extension = dAppConnector?.extensions?.find((extension) => extension.name === '<Extension name>')
if (extension.available) {
await dAppConnector!.connectExtension(extension.id);
const signer = dAppConnector.getSigner(AccountId.fromString('0.0.12345'))

// This request will open the extension
const response = await signer.signAndExecuteTransaction(transaction)
}
```

Wallets that are compatible should be able to receive and respond to the following messages:

- `"hedera-extension-query"`: The extension is required to respond with
`"hedera-extension-response"` and provide the next set of data in the metadata property.
```javascript
let metadata = {
id: '<extesnionId>',
name: '<Wallet name>',
url: '<Wallet url>',
icon: '<Wallet con>',
description: '<Wallet url>',
}
```
- `"hedera-extension-open-<extensionId>"`: The extension needs to listen to this message and
automatically open.
- `"hedera-extension-connect-<extensionId>"`: The extension must listen to this message and
utilize the `pairingString` property in order to establish a connection.

This communication protocol between the wallet and web dApps requires an intermediate script to
use the Chrome API. Refer to the
[Chrome Extensions documentation](https://developer.chrome.com/docs/extensions/develop/concepts/messaging)

## Demo & docs

This repository includes a vanilla html/css/javascript implementation with a dApp and wallet
Expand Down
23 changes: 23 additions & 0 deletions demos/react-dapp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>dApp</title>
<link rel="stylesheet" href="/main.css" />
</head>

<body>
<main>
<h1>dApp</h1>
<p>
This demo dApp requires a project id from WalletConnect. Please see
<a target="_blank" href="https://cloud.walletconnect.com">
https://cloud.walletconnect.com
</a>
</p>
<div id="root"></div>
</main>
<script src="main.js"></script>
</body>
</html>
186 changes: 186 additions & 0 deletions demos/react-dapp/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
* {
box-sizing: border-box;
border-radius: 1rem;
appearance: none;
}
body {
margin: 0;
font-family: sans-serif;
color: white;
background: black;
}
main {
padding: 1rem;
margin: 0 auto;
max-width: 1400px;
}

section {
color: black;
background-color: white;
margin: 3rem 0;
padding: 1rem;
background:
linear-gradient(white, white) padding-box,
linear-gradient(to right, #5281e7, #765aea) border-box;
border: 6px solid transparent;
/* border-radius: 0 10rem 10rem; */
padding: 4vw 6vw;
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, 0.1);
/* nesting natively supported by some browsers */
& form fieldset {
display: flex;
flex-direction: column;

& label {
padding: 1rem 0;
font-weight: bold;
}
}
}

a {
color: #5381e7;
text-decoration: none;
&:hover {
filter: brightness(1.4);
}
}

button {
max-width: fit-content;
color: white;
background: linear-gradient(160deg, #3ec878, #21a056);
position: relative;
z-index: 1;
cursor: pointer;
margin: 1rem 0;
appearance: none;
padding: 0.5rem 1rem;
border: 1px solid transparent;
border-radius: 1rem;
transition: opacity 0.3s;

&::before {
content: '';
background: linear-gradient(160deg, #21a056, #3ec878);
position: absolute;
border-radius: 15px;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
opacity: 0;
transition: opacity 0.3s;
z-index: -1;
}

&:hover::before {
opacity: 1;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}

input,
select {
width: 100%;
height: 2rem;
border: 1px solid black;
margin: 0.5rem 0;
padding: 0.5rem;
&:disabled {
color: transparent;
border: 1px solid lightgrey;
background-color: #fafafa;
}
}
#init input,
#init select {
&:disabled {
color: black;
border: 1px solid black;
}
}

hr {
width: 100%;
}

dialog {
max-width: 60rem;
position: relative;
padding: 1rem;
background:
linear-gradient(white, white) padding-box,
linear-gradient(to right, #5281e7, #765aea) border-box;
border: 6px solid transparent;
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, 0.1);
}

dialog::backdrop {
background: hsl(0 0% 0% / 50%);
}

dialog > div:first-child > button {
color: white;
background: linear-gradient(160deg, #5d5f5f, #2d2d2d);
font-size: 0.75em;

&::before {
content: '';
background: linear-gradient(160deg, #2d2d2d, #5d5f5f);
}
}

dialog > div:first-child {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}

dialog > div:nth-child(2) {
padding: 2rem;
}

.loading {
display: flex;
flex-direction: column;
align-items: center;
}

.loader {
width: 48px;
height: 48px;
border: 3px solid #765aea;
border-radius: 50%;
display: inline-block;
position: relative;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
.loader::after {
content: '';
box-sizing: border-box;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 56px;
height: 56px;
border-radius: 50%;
border: 3px solid transparent;
border-bottom-color: #2d2d2d;
}

@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
11 changes: 11 additions & 0 deletions demos/react-dapp/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './src/App'

document.body.innerHTML = '<div id="app"></div>'

const appElement = document.getElementById('app')
if (appElement) {
const root = createRoot(appElement)
root.render(<App />)
}
Loading