Connect Wallet
-
- If you don't have a wallet, you can select a provider and create one now.{' '}
- props.onClickLearnMore()}>
- Learn more
+ {enableBugReport === 'true' && (
+
+ Do you wish to report a bug?{' '}
+ props.onClickLearnMore()}>
+ Click here
+
+
+ )}
+ {enableBugReport !== 'true' && (
+
+ If you don't have a wallet, you can select a provider and create one now.{' '}
+ props.onClickLearnMore()}>
+ Learn more
+
-
+ )}
diff --git a/packages/beacon-ui/src/index.ts b/packages/beacon-ui/src/index.ts
index 9fa86d773..0c0d230b5 100644
--- a/packages/beacon-ui/src/index.ts
+++ b/packages/beacon-ui/src/index.ts
@@ -18,4 +18,13 @@ export type { ToastAction } from './ui/toast'
export { getColorMode, setColorMode } from './utils/colorMode'
-export { isMobile, isMobileOS, isBrowser, isDesktop } from './utils/platform'
+export {
+ isMobile,
+ isMobileOS,
+ isBrowser,
+ isDesktop,
+ isAndroid,
+ isIOS,
+ currentBrowser,
+ currentOS
+} from './utils/platform'
diff --git a/packages/beacon-ui/src/ui/alert/getDefautlLogo.ts b/packages/beacon-ui/src/ui/alert/getDefautlLogo.ts
new file mode 100644
index 000000000..ce7e9d73a
--- /dev/null
+++ b/packages/beacon-ui/src/ui/alert/getDefautlLogo.ts
@@ -0,0 +1,3 @@
+export default function getDefaultLogo(): string {
+ return 'data:image/svg+xml;base64,PHN2ZyBpZD0iZjYzZTk1YTktZmQxOS00NDg3LWJjMWEtMDllYjJmMDY2NzA0IiBkYXRhLW5hbWU9Ikd1aWRlcyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNjUiIGhlaWdodD0iNjUiIHZpZXdCb3g9IjAgMCA2NSA2NSI+CiAgICA8dGl0bGU+IGJlYWNvbl9sb2dvPC90aXRsZT4KICAgIDxwYXRoIHN0eWxlPSJmaWxsOiByZ2IoMjYsIDEyOCwgMjUwKTsiIGQ9Ik01OS42NiwyOS41MnYxLjA2YzAsLjM3LS4xOCw5LjA3LTQuMTEsMTUuODhTNDQuMTYsNTcuNzUsNDMuODQsNTcuOTNsLS45MS41NC0xLjYxLS45My05LjE5LTUuM2ExNC42OCwxNC42OCwwLDAsMS0xLjYzLTEuMDZoMGExNC4wOSwxNC4wOSwwLDAsMS0xLjI5LTEsMTcuNjMsMTcuNjMsMCwwLDEtMi41Ni0yLjcsMTguNTgsMTguNTgsMCwwLDEtMi41Ni00LjUxLDIxLjUzLDIxLjUzLDAsMCwxLTEuNS05LDYuMjEsNi4yMSwwLDAsMSwyLjkzLS43M2MuMjUsMCwuNDksMCwuNzQsMGExOC4yOSwxOC4yOSwwLDAsMCwxLDcuOTMsMTUuMDYsMTUuMDYsMCwwLDAsNi42Nyw4bDguOTMsNS4xNmMuMi0uMTMuNDMtLjMuNjgtLjQ4LDIuMjYtMS42LDYuNDItNC45Miw4Ljg2LTkuMTNDNTUuMTIsMzkuOTQsNTUuOCwzNCw1NiwzMS41N2wtMy43OS0yLjE5YTIzLDIzLDAsMCwwLC42LTMuODNaIiAvPgogICAgPHBhdGggc3R5bGU9ImZpbGw6IHJnYigyNiwgMTI4LCAyNTApOyIgZD0iTTQ5LjIyLDExLjQzVjIzLjg5YzAsLjY1LDAsMS4zLS4xLDEuOTR2MGMtLjA2LjU1LS4xMywxLjEtLjI0LDEuNjNBMTgsMTgsMCwwLDEsNDcuODIsMzFhMTkuMTEsMTkuMTEsMCwwLDEtMi42Miw0LjQ5LDIxLjM3LDIxLjM3LDAsMCwxLTcuMDcsNS44LDYuMDgsNi4wOCwwLDAsMS0yLjQyLTIuODRBMTguMSwxOC4xLDAsMCwwLDQyLDMzLjdoMGExNS4xMiwxNS4xMiwwLDAsMCwzLjU4LTkuODFWMTMuNmMtLjIxLS4xMS0uNDctLjIxLS43NC0uMzUtMi41Mi0xLjE2LTcuNDctMy4xLTEyLjM2LTMuMS01LjQyLDAtMTEsMi40LTEzLjA4LDMuNDVWMThhMjIuNTQsMjIuNTQsMCwwLDAtMy42MywxLjM4VjExLjQzbC45Mi0uNTJjLjMzLS4xOCw4LTQuMzgsMTUuNzktNC4zOHMxNS40OCw0LjIsMTUuOCw0LjM4WiIgLz4KICAgIDxwYXRoIHN0eWxlPSJmaWxsOiByZ2IoMjYsIDEyOCwgMjUwKTsiIGQ9Ik0xOCwyNi40LDksMzEuNTVxMCwuMzUuMDYuNzhhMzEuNjMsMzEuNjMsMCwwLDAsMy40OSwxMi4yOGMyLjcxLDQuNzEsNy41NSw4LjMsOS41Myw5LjY0bDMuNzktMi4xOWEyMy4zMywyMy4zMywwLDAsMCwzLDIuNDRsLTYuODgsNC0uOTItLjU0Yy0uMzEtLjE4LTcuNzUtNC42OS0xMS42OC0xMS41UzUuMzYsMzAuOTIsNS4zNSwzMC41NlYyOS40OUw3LDI4LjU1bDkuMTctNS4yOWMuNTctLjMyLDEuMTQtLjYxLDEuNzItLjg3YTEzLjQ2LDEzLjQ2LDAsMCwxLDEuNTUtLjYxQTE3LjUyLDE3LjUyLDAsMCwxLDIzLDIwLjkxYTE5LjIsMTkuMiwwLDAsMSw1LjE5LDAsMjEuNDksMjEuNDksMCwwLDEsOC41NSwzLjI0LDYuMjksNi4yOSwwLDAsMS0xLjI1LDMuNTEsMTcuOSwxNy45LDAsMCwwLTcuMy0zLjA2QTE1LjE0LDE1LjE0LDAsMCwwLDE4LDI2LjRaIi8+Cjwvc3ZnPgo='
+}
diff --git a/packages/beacon-ui/src/ui/alert/index.tsx b/packages/beacon-ui/src/ui/alert/index.tsx
index e35cef3ab..ba8936f99 100644
--- a/packages/beacon-ui/src/ui/alert/index.tsx
+++ b/packages/beacon-ui/src/ui/alert/index.tsx
@@ -26,8 +26,9 @@ import * as infoStyles from '../../components/info/styles.css'
import * as qrStyles from '../../components/qr/styles.css'
import * as loaderStyles from '../../components/loader/styles.css'
import * as pairOtherStyles from '../../components/pair-other/styles.css'
+import * as bugReportFormStyles from '../../components/bug-report-form/styles.css'
-import { Serializer, windowRef } from '@airgap/beacon-core'
+import { Logger, Serializer, windowRef } from '@airgap/beacon-core'
import { PostMessageTransport } from '@airgap/beacon-transport-postmessage'
import {
arrangeTopWallets,
@@ -41,6 +42,11 @@ import { getTzip10Link } from '../../utils/get-tzip10-link'
import { isAndroid, isIOS, isMobileOS, isTwBrowser } from '../../utils/platform'
import { getColorMode } from '../../utils/colorMode'
import PairOther from '../../components/pair-other/pair-other'
+import getDefaultLogo from './getDefautlLogo'
+import { parseUri } from '@walletconnect/utils'
+import BugReportForm from '../../components/bug-report-form'
+
+const logger = new Logger('Alert')
// Interfaces
export interface AlertButton {
@@ -81,7 +87,8 @@ const [currentInfo, setCurrentInfo] = createSignal<
'top-wallets' | 'wallets' | 'install' | 'help' | 'qr'
>('top-wallets')
const [analytics, setAnalytics] = createSignal(undefined)
-
+const [displayQrExtra, setDisplayQrExtra] = createSignal(false)
+const [pairingExpired, setPairingExpired] = createSignal(false)
type VoidFunction = () => void
let dispose: null | VoidFunction = null
@@ -112,8 +119,13 @@ const closeAlert = (_: string): Promise => {
/**
* Close all alerts
*/
-const closeAlerts = async (): Promise =>
- new Promise(async (resolve) => {
+const closeAlerts = async (): Promise => {
+ if (currentInfo() === 'help') {
+ console.log('setting status as pairing expired.')
+ setPairingExpired(true)
+ return
+ }
+ return new Promise(async (resolve) => {
if (isServer) {
console.log('DO NOT RUN ON SERVER')
resolve()
@@ -129,6 +141,7 @@ const closeAlerts = async (): Promise =>
}
resolve()
})
+}
/**
* Show an alert
@@ -141,6 +154,9 @@ const openAlert = async (config: AlertConfig): Promise => {
const p2pPayload = config.pairingPayload?.p2pSyncCode()
const wcPayload = config.pairingPayload?.walletConnectSyncCode()
const isOnline = navigator.onLine
+ const areMetricsEnabled = localStorage
+ ? localStorage.getItem(StorageKey.ENABLE_METRICS) === 'true'
+ : false
setAnalytics(config.analytics)
@@ -176,7 +192,9 @@ const openAlert = async (config: AlertConfig): Promise => {
setCurrentInfo('top-wallets')
setCurrentWallet(undefined)
- localStorage.removeItem(StorageKey.LAST_SELECTED_WALLET)
+
+ !config.title.toLowerCase().includes('error') &&
+ localStorage.removeItem(StorageKey.LAST_SELECTED_WALLET)
// Shadow root
const shadowRootEl = document.createElement('div')
@@ -227,6 +245,11 @@ const openAlert = async (config: AlertConfig): Promise => {
style8.textContent = pairOtherStyles.default
shadowRoot.appendChild(style8)
+ // Bug report styles
+ const style9 = document.createElement('style')
+ style8.textContent = bugReportFormStyles.default
+ shadowRoot.appendChild(style9)
+
// Inject font styles
const styleFonts = document.createElement('style')
styleFonts.textContent =
@@ -372,6 +395,20 @@ const openAlert = async (config: AlertConfig): Promise => {
return <>>
}
+ const setInstallState = (wallet?: MergedWallet) => {
+ if (
+ !wallet ||
+ (wallet.types.length <= 1 &&
+ !wallet.types.includes('ios') &&
+ !wallet.types.includes('desktop')) ||
+ (_isMobileOS && wallet.types.length === 1 && wallet.types.includes('desktop'))
+ ) {
+ return
+ }
+
+ setCurrentInfo('install')
+ }
+
const handleClickShowMoreContent = () => {
analytics()?.track('click', 'ui', 'show more wallets')
setShowMoreContent(!showMoreContent())
@@ -394,46 +431,139 @@ const openAlert = async (config: AlertConfig): Promise => {
if (config.closeButtonCallback) config.closeButtonCallback()
}
+ const updateSelectedWalletWithURL = (url: string) => {
+ let wallet = JSON.parse(localStorage.getItem(StorageKey.LAST_SELECTED_WALLET) ?? '{}')
+
+ if (!wallet.key) {
+ return
+ }
+
+ wallet = {
+ ...wallet,
+ url
+ }
+
+ localStorage.setItem(StorageKey.LAST_SELECTED_WALLET, JSON.stringify(wallet))
+ }
+
+ const generateLink = async () => {
+ let uri = (await wcPayload)?.uri ?? ''
+
+ // If the initial URI is invalid, try generating a new one
+ if (!parseUri(uri).symKey) {
+ uri = (await config.pairingPayload?.walletConnectSyncCode())?.uri ?? ''
+ if (!parseUri(uri).symKey) {
+ handleCloseAlert()
+ setTimeout(
+ () =>
+ openAlert({
+ title: 'Error',
+ body: 'Unexpected transport error. Please try again.'
+ }),
+ 500
+ )
+ return null
+ }
+ }
+
+ return uri
+ }
+
const handleNewTab = async (config: AlertConfig, wallet?: MergedWallet) => {
if (!wallet) {
return
}
- if (config.pairingPayload) {
- setIsLoading(true)
- // Noopener feature parameter cannot be used, because Chrome will open
- // about:blank#blocked instead and it will no longer work.
- const newTab = window.open('', '_blank')
+ if (!config.pairingPayload) {
+ return
+ }
- if (newTab) {
- newTab.opener = null
- }
+ setIsLoading(true)
+ // Noopener feature parameter cannot be used, because Chrome will open
+ // about:blank#blocked instead and it will no longer work.
+ const newTab = window.open('', '_blank')
- let link = ''
+ if (newTab) {
+ newTab.opener = null
+ }
- if (wallet.supportedInteractionStandards?.includes('wallet_connect')) {
- const uri = (await wcPayload)?.uri ?? ''
- if (!!uri.length) {
- link = `${wallet.links[OSLink.WEB]}/wc?uri=${encodeURIComponent(uri)}`
- } else {
- handleCloseAlert()
- setTimeout(() => openAlert({
- title: 'Error',
- body: 'Unexpected transport error. Please try again.'
- }), 500)
- return
- }
- } else {
- const serializer = new Serializer()
- const code = await serializer.serialize(await p2pPayload)
- link = getTzip10Link(wallet.links[OSLink.WEB], code)
+ let link = ''
+
+ if (
+ wallet.supportedInteractionStandards?.includes('wallet_connect') &&
+ !wallet.name.toLowerCase().includes('kukai')
+ ) {
+ const uri = await generateLink()
+
+ if (!uri) {
+ return
}
- if (newTab) {
- newTab.location.href = link
- } else {
- window.open(link, '_blank', 'noopener')
+ link = `${wallet.links[OSLink.WEB]}/wc?uri=${encodeURIComponent(uri)}`
+ } else {
+ const serializer = new Serializer()
+ const code = await serializer.serialize(await p2pPayload)
+ link = getTzip10Link(wallet.links[OSLink.WEB], code)
+ }
+
+ if (newTab) {
+ newTab.location.href = link
+ } else {
+ window.open(link, '_blank', 'noopener')
+ }
+
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: wallet.key,
+ name: wallet.name,
+ type: 'web',
+ icon: currentWallet()?.image
+ })
+ )
+ }
+
+ const handleDeepLinking = async (wallet: MergedWallet, uri: string) => {
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: wallet.key,
+ type: 'mobile',
+ icon: wallet.image
+ })
+ )
+
+ if (!wallet.links[OSLink.IOS].length) {
+ const syncCode = currentWallet()?.supportedInteractionStandards?.includes('wallet_connect')
+ ? (await wcPayload)?.uri ?? ''
+ : await new Serializer().serialize(await p2pPayload)
+
+ if (!syncCode.length) {
+ handleCloseAlert()
+ return
}
+
+ setCodeQR(syncCode)
+ setCurrentInfo('qr')
+ setDisplayQrExtra(true)
+
+ return
+ }
+
+ const link = `${wallet.links[OSLink.IOS]}wc?uri=${encodeURIComponent(uri)}`
+ updateSelectedWalletWithURL(`${wallet.links[OSLink.IOS]}wc?uri=`)
+ logger.log('DO DEEPLINK WITH ' + link)
+
+ if (isTwBrowser(window) && isAndroid(window)) {
+ window.location.href = `${uri}`
+ }
+ if (isAndroid(window)) {
+ window.open(link, '_blank', 'noopener')
+ } else {
+ const a = document.createElement('a')
+ a.setAttribute('href', link)
+ a.setAttribute('rel', 'noopener')
+ a.dispatchEvent(new MouseEvent('click', { view: window, bubbles: true, cancelable: true }))
}
}
@@ -442,52 +572,38 @@ const openAlert = async (config: AlertConfig): Promise => {
setShowMoreContent(false)
const wallet = walletList().find((wallet) => wallet.id === id)
setCurrentWallet(wallet)
+
if (wallet?.key) {
analytics()?.track('click', 'ui', 'opened wallet', { key: wallet.key })
- localStorage.setItem(StorageKey.LAST_SELECTED_WALLET, wallet.key)
}
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: wallet?.key,
+ type: 'mobile',
+ icon: currentWallet()?.image
+ })
+ )
+
if (
- wallet?.types.includes('web') &&
- !(
- wallet?.types.includes('extension') ||
- wallet?.types.includes('desktop') ||
- wallet?.types.includes('ios')
- )
+ (wallet?.types.includes('web') && wallet?.types.length === 1) ||
+ (isAndroid(window) && wallet?.name.toLowerCase().includes('kukai'))
) {
handleNewTab(config, wallet)
return
}
if (wallet && wallet.supportedInteractionStandards?.includes('wallet_connect')) {
- const uri = (await wcPayload)?.uri ?? ''
- if (!!uri.length) {
- if (isAndroid(window) || isIOS(window)) {
- let link = `${wallet.links[OSLink.IOS]}wc?uri=${encodeURIComponent(uri)}`
-
- if (isTwBrowser(window) && isAndroid(window)) {
- link = `${uri}`
- window.location.href = link
- } else if (isAndroid(window)) {
- window.open(link, '_blank', 'noopener')
- } else if (isIOS(window)) {
- const a = document.createElement('a')
- a.setAttribute('href', link)
- a.setAttribute('rel', 'noopener')
- a.dispatchEvent(
- new MouseEvent('click', { view: window, bubbles: true, cancelable: true })
- )
- }
+ const uri = await generateLink()
+
+ if (uri) {
+ if (_isMobileOS && wallet.types.includes('ios') && wallet.types.length === 1) {
+ handleDeepLinking(wallet, uri)
} else {
setCodeQR(uri)
- setCurrentInfo('install')
+ setInstallState(wallet)
}
- } else {
- handleCloseAlert()
- setTimeout(() => openAlert({
- title: 'Error',
- body: 'Unexpected transport error. Please try again.'
- }), 500)
}
setIsLoading(false)
} else if (wallet?.types.includes('ios') && _isMobileOS) {
@@ -501,11 +617,13 @@ const openAlert = async (config: AlertConfig): Promise => {
isIOS(window) && wallet.deepLink
? wallet.deepLink
: isAndroid(window)
- ? 'tezos://'
- : wallet.links[OSLink.IOS],
+ ? wallet.links[OSLink.IOS]
+ : 'tezos://',
code
)
+ updateSelectedWalletWithURL(link)
+
if (isAndroid(window)) window.open(link, '_blank', 'noopener')
else if (isIOS(window)) {
const a = document.createElement('a')
@@ -516,17 +634,26 @@ const openAlert = async (config: AlertConfig): Promise => {
)
}
}
+
setIsLoading(false)
} else {
setIsLoading(false)
- setCurrentInfo('install')
+ setInstallState(wallet)
await setDefaultPayload()
}
}
const handleClickOther = async () => {
analytics()?.track('click', 'ui', 'other wallet')
-
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: 'wallet',
+ name: 'wallet',
+ type: 'mobile',
+ icon: getDefaultLogo()
+ })
+ )
setCurrentInfo('qr')
}
@@ -558,6 +685,16 @@ const openAlert = async (config: AlertConfig): Promise => {
windowRef.postMessage(message as any, windowRef.location.origin)
}
}
+
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: currentWallet()?.key,
+ name: currentWallet()?.name,
+ type: 'extension',
+ icon: currentWallet()?.image
+ })
+ )
}
const handleClickInstallExtension = async () => {
@@ -571,12 +708,22 @@ const openAlert = async (config: AlertConfig): Promise => {
setShowMoreContent(false)
analytics()?.track('click', 'ui', 'open desktop', { key: currentWallet()?.key })
- if (p2pPayload) {
+ if (await p2pPayload) {
const serializer = new Serializer()
const code = await serializer.serialize(await p2pPayload)
const link = getTzip10Link(currentWallet()?.deepLink || '', code)
window.open(link, '_blank', 'noopener')
}
+
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: currentWallet()?.key,
+ name: currentWallet()?.name,
+ type: 'desktop',
+ icon: currentWallet()?.image
+ })
+ )
}
const handleClickDownloadDesktopApp = async () => {
@@ -592,6 +739,30 @@ const openAlert = async (config: AlertConfig): Promise => {
.includes(currentWallet()?.firefoxId || '') ||
availableExtensions.map((extension) => extension.id).includes(currentWallet()?.id || '')
+ const QRCode = ({ isMobile }: any) => {
+ localStorage.setItem(
+ StorageKey.LAST_SELECTED_WALLET,
+ JSON.stringify({
+ key: currentWallet()?.key,
+ name: currentWallet()?.name,
+ type: 'mobile',
+ icon: currentWallet()?.image
+ })
+ )
+ return (
+
+ )
+ }
+
const colorMode = getColorMode()
dispose = render(
@@ -631,14 +802,14 @@ const openAlert = async (config: AlertConfig): Promise => {
}
}
>
- {!isMobile() && isOnline && currentWallet()?.types.includes('web') && (
+ {isOnline && currentWallet()?.types.includes('web') && (
handleNewTab(config, currentWallet())
}
@@ -650,7 +821,7 @@ const openAlert = async (config: AlertConfig): Promise => {
border
title={
hasExtension()
- ? `Use Browser Extension`
+ ? `Connect with ${currentWallet()?.name} Browser Extension`
: `Install ${currentWallet()?.name} Wallet`
}
description={
@@ -664,7 +835,7 @@ const openAlert = async (config: AlertConfig): Promise => {
hasExtension()
? [
{
- label: 'Connect now',
+ label: 'Use Extension',
type: 'primary',
onClick: () => handleClickConnectExtension()
}
@@ -682,7 +853,7 @@ const openAlert = async (config: AlertConfig): Promise => {
{!isMobile() && currentWallet()?.types.includes('desktop') && (
=> {
codeQR().length > 0 &&
currentWallet()?.types.includes('ios') &&
(currentWallet()?.types.length as number) > 1 && (
-
+
)}
{!isMobile() &&
codeQR().length > 0 &&
currentWallet()?.types.includes('ios') &&
(currentWallet()?.types.length as number) <= 1 && (
-
+ )}
+ {isMobile() && currentWallet()?.types.includes('ios') && (
+ {
+ const wallet = currentWallet()
+
+ if (!wallet) {
+ return
+ }
+
+ let syncCode = ''
+ if (
+ wallet.supportedInteractionStandards?.includes('wallet_connect')
+ ) {
+ syncCode = (await wcPayload)?.uri ?? ''
+ } else {
+ syncCode = await new Serializer().serialize(await p2pPayload)
+ }
+ handleDeepLinking(wallet, syncCode)
+ }
+ }
+ ]}
+ downloadLink={
+ currentWallet()?.name.toLowerCase().includes('kukai') && isIOS(window)
+ ? {
+ label: 'Get Kukai Mobile >',
+ url: 'https://ios.kukai.app'
+ }
+ : undefined
+ }
+ onShowQRCodeClick={async () => {
+ const syncCode =
currentWallet()?.supportedInteractionStandards?.includes(
'wallet_connect'
- ) || false
+ )
+ ? (await wcPayload)?.uri ?? ''
+ : await new Serializer().serialize(await p2pPayload)
+
+ const wallet = currentWallet()
+
+ if (!syncCode.length || !wallet) {
+ handleCloseAlert()
+ return
}
- isMobile={true}
- walletName={currentWallet()?.name || 'AirGap'}
- code={codeQR()}
- onClickLearnMore={handleClickLearnMore}
- onClickQrCode={handleClickQrCode}
- />
- )}
- {isMobile() && codeQR().length > 0 && (
-
)}
@@ -774,12 +975,16 @@ const openAlert = async (config: AlertConfig): Promise
=> {
}
}
>
-
+ {!displayQrExtra() ? (
+
+ ) : (
+
+ )}
)}
=> {
onClickShowMore={handleClickShowMoreContent}
onCloseClick={() => handleCloseAlert()}
onBackClick={
- currentInfo() === 'install' && !isMobile()
+ currentInfo() === 'install'
? () => setCurrentInfo('top-wallets')
: currentInfo() === 'qr'
? () => setCurrentInfo('top-wallets')
- : currentInfo() === 'install' && isMobile()
- ? () => setCurrentInfo('wallets')
: currentInfo() === 'wallets' && isMobile()
? () => setCurrentInfo('top-wallets')
: currentInfo() === 'help'
- ? () => setCurrentInfo(previousInfo())
+ ? () => {
+ if (pairingExpired()) {
+ handleCloseAlert()
+ return
+ }
+ return setCurrentInfo(previousInfo())
+ }
: undefined
}
/>
diff --git a/packages/beacon-ui/src/utils/platform.ts b/packages/beacon-ui/src/utils/platform.ts
index 26f3b6394..aae853dba 100644
--- a/packages/beacon-ui/src/utils/platform.ts
+++ b/packages/beacon-ui/src/utils/platform.ts
@@ -56,3 +56,42 @@ export const isMobileOS = (win: Window): boolean =>
win,
/(Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|Tablet|Windows Phone|SymbianOS|Kindle)/i
)
+
+export const currentOS = () => {
+ var ua = navigator.userAgent
+ var osMap = new Map([
+ ['Windows', 'Windows'],
+ ['Mac', 'Mac OS'],
+ ['Linux', 'Linux'],
+ ['iPhone', 'iOS'],
+ ['iPad', 'iOS'],
+ ['Android', 'Android']
+ ])
+
+ for (let [key, value] of osMap) {
+ if (ua.indexOf(key) !== -1) {
+ return value
+ }
+ }
+ return 'UNKOWN'
+}
+
+export const currentBrowser = () => {
+ var ua = navigator.userAgent
+ var browserMap = new Map([
+ ['Firefox', 'Firefox'],
+ ['Opera', 'Opera'],
+ ['OPR', 'Opera'],
+ ['Trident', 'Internet Explorer'],
+ ['Edge', 'Edge'],
+ ['Chrome', 'Chrome'],
+ ['Safari', 'Safari']
+ ])
+
+ for (let [key, value] of browserMap) {
+ if (ua.indexOf(key) !== -1) {
+ return value
+ }
+ }
+ return 'UNKOWN'
+}
diff --git a/packages/beacon-utils/__tests__/beacon-utils.spec.ts b/packages/beacon-utils/__tests__/beacon-utils.spec.ts
index 5fc8ae93a..33d35a5b9 100644
--- a/packages/beacon-utils/__tests__/beacon-utils.spec.ts
+++ b/packages/beacon-utils/__tests__/beacon-utils.spec.ts
@@ -3,7 +3,12 @@
import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import 'mocha'
-import { getAddressFromPublicKey, isValidAddress, prefixPublicKey } from '../src/utils/crypto'
+import {
+ encodePoeChallengePayload,
+ getAddressFromPublicKey,
+ isValidAddress,
+ prefixPublicKey
+} from '../src/utils/crypto'
import { generateGUID } from '../src/utils/generate-uuid'
// use chai-as-promised plugin
@@ -132,4 +137,12 @@ describe(`Crypto`, () => {
expect(isValid).to.deep.equal(true)
})
+
+ describe('encodePoeChallengePayload', () => {
+ it('should encode the payload', () => {
+ expect(encodePoeChallengePayload('hello world')).to.equal(
+ 'mSUN2NQ83VXiP7WZWS4Er7QmWpEtGG5TVY'
+ )
+ })
+ })
})
diff --git a/packages/beacon-utils/package.json b/packages/beacon-utils/package.json
index cb2d0f6ed..cb8e5a8b8 100644
--- a/packages/beacon-utils/package.json
+++ b/packages/beacon-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@airgap/beacon-utils",
- "version": "4.1.2",
+ "version": "4.2.0",
"description": "This package contains utility functions that are used throughout the beacon-sdk",
"author": "Andreas Gassmann
",
"homepage": "https://walletbeacon.io",
diff --git a/packages/beacon-utils/src/index.ts b/packages/beacon-utils/src/index.ts
index a79f06d0d..94d03b2ab 100644
--- a/packages/beacon-utils/src/index.ts
+++ b/packages/beacon-utils/src/index.ts
@@ -12,7 +12,8 @@ export {
recipientString,
signMessage,
isValidAddress,
- prefixPublicKey
+ prefixPublicKey,
+ encodePoeChallengePayload
} from './utils/crypto'
export { generateGUID } from './utils/generate-uuid'
diff --git a/packages/beacon-utils/src/utils/crypto.ts b/packages/beacon-utils/src/utils/crypto.ts
index a6834c488..141d16fe1 100644
--- a/packages/beacon-utils/src/utils/crypto.ts
+++ b/packages/beacon-utils/src/utils/crypto.ts
@@ -12,6 +12,9 @@ import { sign } from '@stablelib/ed25519'
export const secretbox_NONCEBYTES = 24 // crypto_secretbox_NONCEBYTES
export const secretbox_MACBYTES = 16 // crypto_secretbox_MACBYTES
+const POE_CHALLENGE_BYTES_LENGTH = 20
+const POE_CHALLENGE_PREFIX = 110
+
/* eslint-disable prefer-arrow/prefer-arrow-functions */
/**
@@ -278,4 +281,15 @@ export const isValidAddress = (address: string): boolean => {
return true
}
+export function encodePoeChallengePayload(payload: string) {
+ const poeBlake2b = new BLAKE2b(POE_CHALLENGE_BYTES_LENGTH)
+
+ return bs58check.encode(
+ Buffer.concat([
+ new Uint8Array([POE_CHALLENGE_PREFIX]),
+ Buffer.from(poeBlake2b.update(Buffer.from(payload)).digest())
+ ])
+ )
+}
+
/* eslint-enable prefer-arrow/prefer-arrow-functions */
diff --git a/packages/beacon-wallet/package.json b/packages/beacon-wallet/package.json
index e6f47aec1..267b310c4 100644
--- a/packages/beacon-wallet/package.json
+++ b/packages/beacon-wallet/package.json
@@ -1,6 +1,6 @@
{
"name": "@airgap/beacon-wallet",
- "version": "4.1.2",
+ "version": "4.2.0",
"description": "Use this package in your wallet to instanciate a WalletClient object and communicate to dApps.",
"author": "Andreas Gassmann ",
"homepage": "https://walletbeacon.io",
@@ -35,8 +35,8 @@
"url": "https://github.com/airgap-it/beacon-sdk/issues"
},
"dependencies": {
- "@airgap/beacon-core": "4.1.2",
- "@airgap/beacon-transport-matrix": "4.1.2",
- "@airgap/beacon-transport-postmessage": "4.1.2"
+ "@airgap/beacon-core": "4.2.0",
+ "@airgap/beacon-transport-matrix": "4.2.0",
+ "@airgap/beacon-transport-postmessage": "4.2.0"
}
}
diff --git a/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts b/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts
index 18ee73f73..31baa8ffe 100644
--- a/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts
+++ b/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts
@@ -12,11 +12,11 @@ import {
BeaconMessageWrapper,
BlockchainRequestV3,
PermissionRequestV3,
- BeaconBaseMessage,
- ProofOfEventChallengeRecordedMessageOutput
+ BeaconBaseMessage
// EncryptPayloadRequestOutput
} from '@airgap/beacon-types'
import { AppMetadataManager, Logger } from '@airgap/beacon-core'
+import { SimulatedProofOfEventChallengeRequestOutput } from '@airgap/beacon-types/dist/esm/types/beacon/messages/BeaconRequestOutputMessage'
const logger = new Logger('IncomingRequestInterceptor')
@@ -156,13 +156,26 @@ export class IncomingRequestInterceptor {
interceptorCallback(request, connectionInfo)
}
break
- case BeaconMessageType.ProofOfEventChallengeRecorded:
+ case BeaconMessageType.SimulatedProofOfEventChallengeRequest:
{
const appMetadata: AppMetadata = await IncomingRequestInterceptor.getAppMetadata(
appMetadataManager,
message.senderId
)
- const request: ProofOfEventChallengeRecordedMessageOutput = {
+ const request: SimulatedProofOfEventChallengeRequestOutput = {
+ appMetadata,
+ ...message
+ }
+ interceptorCallback(request, connectionInfo)
+ }
+ break
+ case BeaconMessageType.SimulatedProofOfEventChallengeRequest:
+ {
+ const appMetadata: AppMetadata = await IncomingRequestInterceptor.getAppMetadata(
+ appMetadataManager,
+ message.senderId
+ )
+ const request: SimulatedProofOfEventChallengeRequestOutput = {
appMetadata,
...message
}
diff --git a/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts b/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts
index aaf635ea3..025c1a3fe 100644
--- a/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts
+++ b/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts
@@ -22,7 +22,8 @@ import {
BlockchainResponseV3,
PermissionResponseV3,
BeaconBaseMessage,
- ProofOfEventChallengeResponse
+ ProofOfEventChallengeResponse,
+ SimulatedProofOfEventChallengeResponse
// EncryptPayloadResponse
} from '@airgap/beacon-types'
import { getAddressFromPublicKey, CONTRACT_PREFIX, isValidAddress } from '@airgap/beacon-utils'
@@ -279,6 +280,16 @@ export class OutgoingResponseInterceptor {
interceptorCallback(response)
}
break
+ case BeaconMessageType.SimulatedProofOfEventChallengeResponse:
+ {
+ const response: SimulatedProofOfEventChallengeResponse = {
+ senderId,
+ version: '2',
+ ...message
+ }
+ interceptorCallback(response)
+ }
+ break
default:
logger.log('intercept', 'Message not handled')
assertNever(message)
diff --git a/readme.md b/readme.md
index 5cadb810a..26bd42505 100644
--- a/readme.md
+++ b/readme.md
@@ -34,15 +34,23 @@ npm i --save @airgap/beacon-sdk
## Example DApp integration
```ts
-const client = new DAppClient({ name: 'My Sample DApp' })
-
-client
- .requestPermissions() // Send a permission request and automatically show UI to the user to select his favorite wallet
- .then((permissions) => {
- // Account that has been shared by the wallet
- console.log('got permissions', permissions)
- })
- .catch((error) => console.log(error))
+import { DAppClient } from '@airgap/beacon-sdk'
+
+const dAppClient = new DAppClient({ name: 'My Sample DApp' })
+
+// Listen for all the active account changes
+dAppClient.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, async (account) => {
+ // An active account has been set, update the dApp UI
+ console.log(`${BeaconEvent.ACTIVE_ACCOUNT_SET} triggered: `, account)
+})
+
+try {
+ console.log('Requesting permissions...')
+ const permissions = await dAppClient.requestPermissions()
+ console.log('Got permissions:', permissions.address)
+} catch (error) {
+ console.error('Got error:', error)
+}
```
For a more complete example, take a look at the `example-dapp.html` file.
diff --git a/scripts/blockchains/tezos.ts b/scripts/blockchains/tezos.ts
index 81050a187..f174681e7 100644
--- a/scripts/blockchains/tezos.ts
+++ b/scripts/blockchains/tezos.ts
@@ -83,6 +83,7 @@ export const tezosWebList: WebApp[] = [
shortName: 'Kukai',
color: '',
logo: 'web-kukai.png',
+ supportedInteractionStandards: ['wallet_connect'],
links: {
[NetworkType.MAINNET]: 'https://wallet.kukai.app',
[NetworkType.GHOSTNET]: 'https://ghostnet.kukai.app',
@@ -251,6 +252,26 @@ export const tezosIosList: App[] = [
supportedInteractionStandards: ['beacon'],
deepLink: 'exodus://wc',
universalLink: 'https://www.exodus.com/'
+ },
+ {
+ key: 'kukai_ios',
+ name: 'Kukai Wallet',
+ shortName: 'Kukai',
+ color: '',
+ logo: 'web-kukai.png',
+ supportedInteractionStandards: ['wallet_connect'],
+ universalLink: 'https://wallet.kukai.app',
+ deepLink: 'kukai://'
+ },
+ {
+ key: 'fireblocks_ios',
+ name: 'Fireblocks Wallet',
+ shortName: 'Fireblocks',
+ color: '',
+ logo: 'ios-fireblocks.svg',
+ supportedInteractionStandards: ['wallet_connect'],
+ universalLink: '',
+ deepLink: undefined
}
// {
// name: 'Galleon',