@@ -83,6 +91,7 @@ export class SelectIssuers extends AbstractView {
const refreshBtn = this.viewContainer.querySelector('.refresh-tn')
refreshBtn.addEventListener('click', () => {
+ this.client.eventSender('tokens-refreshed', null)
this.autoLoadTokens(true)
})
@@ -98,7 +107,7 @@ export class SelectIssuers extends AbstractView {
let tokensListElem = this.tokensContainer.getElementsByClassName('token-list-container-tn')[0]
- this.tokenListView = new TokenList(this.client, this.ui, tokensListElem, {})
+ this.tokenListView = new TokenList(this.client, this.ui, tokensListElem, { ...this.params })
}
protected afterRender() {
@@ -114,17 +123,11 @@ export class SelectIssuers extends AbstractView {
}
async setupWalletButton() {
- const provider = await this.client.getWalletProvider()
-
- if (provider.getConnectedWalletData().length > 0) {
- const walletBtn = this.viewContainer.querySelector('.dis-wallet-tn')
-
- walletBtn.style.display = 'block'
-
- walletBtn.addEventListener('click', () => {
- this.client.disconnectWallet()
- })
- }
+ const walletBtn = this.viewContainer.querySelector('.dis-wallet-tn')
+ walletBtn.style.display = 'block'
+ walletBtn.addEventListener('click', () => {
+ this.client.disconnectWallet()
+ })
}
issuersLoading() {
@@ -360,7 +363,7 @@ export class SelectIssuers extends AbstractView {
}
})
- this.tokenListView?.update({ issuer: issuer, tokens: tokens })
+ this.tokenListView?.update({ data: { issuer: issuer, tokens: tokens } })
}
showTokenView(issuer: string) {
diff --git a/src/client/views/select-wallet.ts b/src/client/views/select-wallet.ts
index fdead6b0..fdd0fde4 100644
--- a/src/client/views/select-wallet.ts
+++ b/src/client/views/select-wallet.ts
@@ -1,18 +1,28 @@
import { AbstractView } from './view-interface'
import { logger } from '../../utils'
+import { getBrowserData } from '../../utils/support/getBrowserData'
+import { UIUpdateEventType } from '../index'
const phantomSVG =
'
Phantom logo '
-const metaMaskSVG =
- '
Metamask logo '
+
+const metaMask = {
+ imgBig: `
+
Metamask logo
+ `,
+ label: 'MetaMask',
+}
+
const walletConnectSVG =
'
Wallet connect logo '
const walletConnectV2SVG =
'
'
const torusSVG =
'
Torus logo '
-const safeConnectSVG =
- '
Safe connect button '
+const safeConnectSVG = `
+
Safe connect button
+`
+
const flowSVG = `
+ `,
+ label: 'AlphaWallet',
+}
+
+const statusWallet = {
+ imgBig: `
+
+ `,
+ label: 'Status Wallet',
+}
+
+const trustWallet = {
+ imgBig: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ label: 'Trust Wallet',
+}
+
+const ethereumWallet = {
+ imgBig: `
+
+ `,
+ label: 'Embedded Wallet',
+}
+
+const braveWallet = {
+ imgBig: `
+
+
+
+ build-icons/Stable Copy 3
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ label: 'Brave',
+}
+
+const mew = {
+ imgBig: `
+
+ `,
+ label: 'MEW',
+}
+
export class SelectWallet extends AbstractView {
+ init() {
+ this.client.registerUiUpdateCallback(UIUpdateEventType.WALLET_DISCONNECTED, undefined)
+ }
+
render() {
- // TODO only show the wallets that relate to the tokens available.
+ const evmOrOffChain = this.client.hasIssuerForBlockchain('evm')
- const MetaMaskButton =
- typeof window.ethereum !== 'undefined'
- ? `
- ${metaMaskSVG}
- MetaMask
+ const SafeConnectButton = this.client.safeConnectAvailable()
+ ? `
+ ${safeConnectSVG}
+ Safe Connect
+ `
+ : ''
+
+ const browser = getBrowserData()
+
+ // default fallback icon
+ let injectedWallet = ethereumWallet
+
+ // we detect brave by window.navigator['brave'], have overwrite it later if metamask enabled
+ if (browser.brave) injectedWallet = braveWallet
+ if (browser.anyMetamask) injectedWallet = metaMask
+ if (browser.mew) injectedWallet = mew
+ if (browser.trust) injectedWallet = trustWallet
+ if (browser.status) injectedWallet = statusWallet
+ if (browser.alphaWallet) injectedWallet = alphaWallet
+ if (browser.imToken) injectedWallet = imToken
+
+ const InjectedWalletButton =
+ typeof window.ethereum !== 'undefined' && evmOrOffChain
+ ? `
+ ${injectedWallet.imgBig}
+ ${injectedWallet.label}
`
: ''
- const PhantomButton = this.client.solanaAvailable()
+ const walletConnectButton = evmOrOffChain
+ ? `
+ ${walletConnectSVG}
+ Wallet Connect
+ `
+ : ''
+
+ const walletConnectV2Button = evmOrOffChain
+ ? `
+ ${walletConnectV2SVG}
+ Wallet Connect V2
+ `
+ : ''
+
+ const torusButton = evmOrOffChain
+ ? `
+ ${torusSVG}
+ Torus
+ `
+ : ''
+
+ const PhantomButton = this.client.hasIssuerForBlockchain('solana')
? `
${phantomSVG}
Phantom
`
: ''
- const SafeConnectButton = this.client.safeConnectAvailable()
- ? `
- ${safeConnectSVG}
- Safe Connect
- `
- : ''
-
- const FlowButton = `
+ // TODO stop connect to Flow when popup closed
+ const FlowButton = this.client.hasIssuerForBlockchain('flow')
+ ? `
${flowSVG}
Flow
`
+ : ''
this.viewContainer.innerHTML = `
@@ -79,21 +227,12 @@ export class SelectWallet extends AbstractView {
@@ -121,27 +260,28 @@ export class SelectWallet extends AbstractView {
async connectWallet(e: any) {
let wallet: any = e.currentTarget.dataset.wallet
+ let walletlabel: string = e.currentTarget.dataset.walletlabel
+ walletlabel = walletlabel ?? wallet
logger(2, 'Connect wallet: ' + wallet)
this.ui.showLoaderDelayed(
- ['
- ${this.params.options.buttonText ?? "Let's go!"}
+ ${this.params.options.openingButtonText ?? "Let's go!"}
${this.params.options.openingHeading}
@@ -24,12 +24,13 @@ export class Start extends AbstractView {
async goToWalletSelection() {
this.ui.showLoaderDelayed(['Initializing wallet..'], 500)
+ const opt = { viewTransition: 'slide-in-right' }
if (await this.ui.canSkipWalletSelection()) {
this.client.enrichTokenLookupDataOnChainTokens()
- this.ui.updateUI('main')
+ this.ui.updateUI('main', { viewName: 'main' }, opt)
} else {
- this.ui.updateUI('wallet')
+ this.ui.updateUI('wallet', { viewName: 'main' }, opt)
}
this.ui.dismissLoader()
diff --git a/src/client/views/token-list.ts b/src/client/views/token-list.ts
index 4b82ad74..faecd7c5 100644
--- a/src/client/views/token-list.ts
+++ b/src/client/views/token-list.ts
@@ -62,15 +62,15 @@ export class TokenList extends AbstractView {
getTokenListItems() {
let html = ''
- let newCount = Math.min(this.loadedCount + this.numberToLoad, this.params.tokens.length)
+ let newCount = Math.min(this.loadedCount + this.numberToLoad, this.params.data.tokens.length)
for (let i: number = this.loadedCount; i < newCount; i++) {
- html += this.createTokenMarkup(this.params.tokens[i])
+ html += this.createTokenMarkup(this.params.data.tokens[i])
}
this.loadedCount = newCount
- if (this.loadedCount < this.params.tokens.length) html += this.createLoadMoreMarkup()
+ if (this.loadedCount < this.params.data.tokens.length) html += this.createLoadMoreMarkup()
return html
}
diff --git a/src/client/views/view-interface.ts b/src/client/views/view-interface.ts
index 00a67959..a777c114 100644
--- a/src/client/views/view-interface.ts
+++ b/src/client/views/view-interface.ts
@@ -1,5 +1,5 @@
import { Client } from '../index'
-import { Ui } from '../ui'
+import { Ui, UIOptionsInterface } from '../ui'
export interface ViewConstructor
{
new (client: Client, popup: Ui, viewContainer: any, params: any): T
@@ -12,20 +12,26 @@ export type ViewComponent = ViewFactory | ViewConstructor
export interface ViewInterface {
client: Client
ui: Ui
- viewContainer: any
- params: any
+ viewContainer: HTMLDivElement | any
+ params: IViewParameters
render(): void
init(): void
- update(params: any): void
+ update(params: IViewParameters): void
+}
+
+export interface IViewParameters {
+ options: UIOptionsInterface | any
+ viewOptions: any
+ data?: any
}
export abstract class AbstractView implements ViewInterface {
client: Client
ui: Ui
- viewContainer: any
- params: any = {}
+ viewContainer: HTMLDivElement | any
+ params: IViewParameters
- constructor(client: Client, popup: Ui, viewContainer: any, params: any) {
+ constructor(client: Client, popup: Ui, viewContainer: HTMLDivElement | any, params: IViewParameters) {
this.client = client
this.ui = popup
this.viewContainer = viewContainer
@@ -35,13 +41,13 @@ export abstract class AbstractView implements ViewInterface {
// eslint-disable-next-line @typescript-eslint/no-empty-function
public init(): void {
- /* TODO document why this method 'init' is empty */
+ // Init can be used to implement extra constructor code without overriding the constructor
}
abstract render(): void
- public update(params: any): void {
- this.params = params
+ public update(params: Partial): void {
+ this.params = { ...this.params, ...params }
this.render()
}
}
diff --git a/src/core/__tests__/core.spec.ts b/src/core/__tests__/core.spec.ts
index d3328503..2dd052b1 100644
--- a/src/core/__tests__/core.spec.ts
+++ b/src/core/__tests__/core.spec.ts
@@ -2,7 +2,7 @@
import { SignedDevconTicket } from '@tokenscript/attestation/dist/asn1/shemas/SignedDevconTicket'
-import { filterTokens, readTokens, decodeTokens, storeMagicURL, readMagicUrl, ethKeyIsValid } from './../index'
+import { filterTokens, readTokens, decodeTokens, storeMagicURL, readTokenFromMagicUrl, ethKeyIsValid } from './../index'
import { readSignedTicket } from '../../outlet'
// const mockToken = `?ticket="MIGWMA0MATYCBWE3ap3-AgEABEEEKJZVxMEXbkSZZBWnNUTX_5ieu8GUqf0bx_a0tBPF6QYskABaMJBYhDOXsmQt3csk_TfMZ2wdmfRkK7ePCOI2kgNCAOOZKRpcE6tLBuPbfE_SmwPk2wNjbj5vpa6kkD7eqQXvBOCa0WNo8dEHKvipeUGZZEWWjJKxooB44dEYdQO70Vgc"&secret=45845870684&id="mah@mah.com"`
@@ -113,23 +113,13 @@ describe('core Spec', () => {
// Jest Test onerror
// https://stackoverflow.com/questions/28584773/xmlhttprequest-testing-in-jest
- test('expect to read new magic link', () => {
+ test('expect to read token from a magic link', () => {
window.history.pushState(
{},
'Test Title',
'/?ticket=MIGSMAkMATkCAQUCAQwEQQQsUB1tp0mEn0Zoc8Lu-c0ZJOHis3ynlUAuplV8jpJhMgGMuP4i2msZihJq0VeBBOhGLU-vhfkn_0DYsJ9J8djgA0IAScs-3TwdMQ6XSIu1z1nDRCWEzAMBWaVEHONiRlW0j5kTEXBKvgNHS5DsjGm2S84BKqHl3qucBHUOGjpt-6hEuxw=&secret=285996413010999512790264856198259265088323878963947294417758116344175800611&id=nicktaras83@gmail.com',
)
- readMagicUrl('ticket', 'secret', 'id', 'dcTokens')
+ const token = readTokenFromMagicUrl('ticket', 'secret', 'id')
+ expect(token.id).toEqual('nicktaras83@gmail.com')
})
-
- test('expect to re-read magic link which is not pushed to state', () => {
- window.history.pushState(
- {},
- 'Test Title',
- '/?ticket=MIGSMAkMATkCAQUCAQwEQQQsUB1tp0mEn0Zoc8Lu-c0ZJOHis3ynlUAuplV8jpJhMgGMuP4i2msZihJq0VeBBOhGLU-vhfkn_0DYsJ9J8djgA0IAScs-3TwdMQ6XSIu1z1nDRCWEzAMBWaVEHONiRlW0j5kTEXBKvgNHS5DsjGm2S84BKqHl3qucBHUOGjpt-6hEuxw=&secret=285996413010999512790264856198259265088323878963947294417758116344175800611&id=nicktaras83@gmail.com',
- )
- readMagicUrl('ticket', 'secret', 'id', 'dcTokens')
- // try to add again. This time it will exist and not be added to array.
- readMagicUrl('ticket', 'secret', 'id', 'dcTokens')
- })
-})
+})
\ No newline at end of file
diff --git a/src/core/index.ts b/src/core/index.ts
index 2ba362d5..7d1e2a44 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -1,16 +1,32 @@
import { base64ToUint8array, compareObjects } from '../utils/index'
+import { OutletInterface } from '../outlet'
+
+export interface OffChainTokenData {
+ token: string
+ secret: string
+ id: string
+ magic_link: string
+}
+
+export interface DecodedToken {
+ devconId: string
+ ticketIdNumber?: string
+ ticketIdString?: number
+ ticketClass: number
+ commitment: Uint8Array
+}
interface FilterInterface {
[key: string]: any
}
-export const filterTokens = (decodedTokens: any, filter: FilterInterface = []) => {
- let res: any = []
+export const filterTokens = (decodedTokens: DecodedToken[], filter: FilterInterface = {}) => {
+ let res: DecodedToken[] = []
if (decodedTokens.length && typeof filter === 'object' && Object.keys(filter).length) {
let filterKeys = Object.keys(filter)
- decodedTokens.forEach((token: any) => {
+ decodedTokens.forEach((token: DecodedToken) => {
let fitFilter = 1
filterKeys.forEach((key) => {
@@ -26,19 +42,23 @@ export const filterTokens = (decodedTokens: any, filter: FilterInterface = []) =
}
}
-export const readTokens = (itemStorageKey: any) => {
+export const readTokens = (itemStorageKey: string) => {
const storageTickets = localStorage.getItem(itemStorageKey)
- let tokens: any = []
+ let tokens: OffChainTokenData[] = []
- let output: any = { tokens: [], noTokens: true, success: true }
+ let output: { tokens: OffChainTokenData[]; noTokens: boolean; success: boolean } = {
+ tokens: [],
+ noTokens: true,
+ success: true,
+ }
try {
if (storageTickets && storageTickets.length) {
tokens = JSON.parse(storageTickets)
if (tokens.length !== 0) {
- tokens.forEach((item: any) => {
+ tokens.forEach((item: OffChainTokenData) => {
if (item.token && item.secret) output.tokens.push(item)
})
}
@@ -55,7 +75,7 @@ export const readTokens = (itemStorageKey: any) => {
}
export const decodeTokens = (
- rawTokens: any,
+ rawTokens: string,
tokenParser: any,
unsignedTokenDataName: string,
includeSignedToken = false,
@@ -65,15 +85,7 @@ export const decodeTokens = (
if (x.length) {
return x.map((tokenData: any) => {
if (tokenData.token) {
- let decodedToken = new tokenParser(base64ToUint8array(tokenData.token).buffer)
-
- if (decodedToken && decodedToken[unsignedTokenDataName]) {
- let token = decodedToken[unsignedTokenDataName]
-
- token = propsArrayBufferToArray(token)
-
- return includeSignedToken ? { signedToken: tokenData.token, ...token } : token
- }
+ return decodeToken(tokenData, tokenParser, unsignedTokenDataName, includeSignedToken)
}
})
} else {
@@ -81,7 +93,26 @@ export const decodeTokens = (
}
}
-function propsArrayBufferToArray(obj: any) {
+export const decodeToken = (
+ tokenData: OffChainTokenData,
+ tokenParser: any,
+ unsignedTokenDataName: string,
+ includeSignedToken = false,
+) => {
+ if (tokenData.token) {
+ let decodedToken = new tokenParser(base64ToUint8array(tokenData.token).buffer)
+
+ if (decodedToken && decodedToken[unsignedTokenDataName]) {
+ let token = decodedToken[unsignedTokenDataName]
+
+ token = propsArrayBufferToArray(token)
+
+ return includeSignedToken ? { signedToken: tokenData.token, ...token } : token
+ }
+ }
+}
+
+function propsArrayBufferToArray(obj: { [key: string]: any }) {
Object.keys(obj).forEach((key) => {
if (obj[key] instanceof ArrayBuffer) {
obj[key] = Array.from(new Uint8Array(obj[key]))
@@ -90,19 +121,18 @@ function propsArrayBufferToArray(obj: any) {
return obj
}
-export const storeMagicURL = (tokens: any, itemStorageKey: string) => {
+export const storeMagicURL = (tokens: OffChainTokenData[], itemStorageKey: string) => {
if (tokens) {
localStorage.setItem(itemStorageKey, JSON.stringify(tokens))
}
}
-export const readMagicUrl = (
+export const readTokenFromMagicUrl = (
tokenUrlName: string,
tokenSecretName: string,
tokenIdName: string,
- itemStorageKey: string,
urlParams: URLSearchParams | null = null,
-) => {
+): OffChainTokenData => {
if (urlParams === null) urlParams = new URLSearchParams(window.location.search)
const tokenFromQuery = urlParams.get(tokenUrlName)
@@ -114,40 +144,15 @@ export const readMagicUrl = (
if (!(tokenFromQuery && secretFromQuery)) throw new Error('Incomplete token params in URL.')
- let tokensOutput = readTokens(itemStorageKey)
-
- let isNewQueryTicket = true
-
- // TODO: use loop here instead
- let tokens = tokensOutput.tokens.map((tokenData: any) => {
- if (tokenData.token === tokenFromQuery) {
- isNewQueryTicket = false
- }
-
- return tokenData
- })
-
- if (isNewQueryTicket) {
- tokens.push({
- token: tokenFromQuery,
- secret: secretFromQuery,
- id: decodeURIComponent(idFromQuery),
- magic_link: window.location.href,
- })
- return tokens
+ return {
+ token: tokenFromQuery,
+ secret: secretFromQuery,
+ id: decodeURIComponent(idFromQuery),
+ magic_link: window.location.href,
}
-
- throw new Error('Token already added.')
}
-export const rawTokenCheck = async (unsignedToken: any, tokenIssuer: any) => {
- // currently meta mask is needed to move beyond this point.
- // however the err msg given is not obvious that this is the issue.
-
- // metamask is only one of the wallets, we have to use walletConnect to
- // check if user have some wallet immediately before use wallet
- // requiredParams(window.ethereum, "Please install metamask to continue.");
-
+export const rawTokenCheck = async (unsignedToken: DecodedToken, tokenIssuer: OutletInterface) => {
let rawTokenData = getRawToken(unsignedToken, tokenIssuer)
if (!rawTokenData) return null
@@ -179,7 +184,7 @@ export const rawTokenCheck = async (unsignedToken: any, tokenIssuer: any) => {
return tokenObj
}
-export const getRawToken = (unsignedToken: any, tokenIssuer: any) => {
+export const getRawToken = (unsignedToken: DecodedToken, tokenIssuer: OutletInterface): OffChainTokenData => {
if (!unsignedToken || !Object.keys(unsignedToken).length) return
let tokensOutput = readTokens(tokenIssuer.itemStorageKey)
@@ -190,7 +195,7 @@ export const getRawToken = (unsignedToken: any, tokenIssuer: any) => {
let token = {}
if (rawTokens.length) {
- rawTokens.forEach((tokenData: any) => {
+ rawTokens.forEach((tokenData: OffChainTokenData) => {
if (tokenData.token) {
const _tokenParser = tokenIssuer.tokenParser
@@ -208,7 +213,7 @@ export const getRawToken = (unsignedToken: any, tokenIssuer: any) => {
})
}
- return token
+ return token as OffChainTokenData
}
return null
diff --git a/src/core/messaging.ts b/src/core/messaging.ts
index 41e251c2..04844924 100644
--- a/src/core/messaging.ts
+++ b/src/core/messaging.ts
@@ -34,6 +34,9 @@ declare global {
}
}
+/* URL namespace is used to avoid possible collisions with URL parameter names */
+export const URLNS = 'tn-'
+
export class Messaging {
iframeStorageSupport: null | boolean = null
iframe: any = null
@@ -83,7 +86,7 @@ export class Messaging {
let id = Messaging.getUniqueEventId()
const url = this.constructUrl(id, request)
- let newLocation = `${url}&redirect=true&requestor=${encodeURIComponent(redirectUrl)}`
+ let newLocation = `${url}&${URLNS}redirect=true&${URLNS}requestor=${encodeURIComponent(redirectUrl)}`
logger(2, `redirect from ${document.location.href} to ${newLocation}`)
@@ -273,12 +276,14 @@ export class Messaging {
}
}
+ // TODO: Use URLSearchParams object to build this query rather than manually constructing it
+ // This will prevent edge-case encoding issues.
private constructUrl(id: any, request: RequestInterfaceBase) {
- let url = `${request.origin}#evtid=${id}&action=${request.action}`
+ let url = `${request.origin}#${URLNS}evtid=${id}&${URLNS}action=${request.action}`
// in request to Outlet() to get tokens we dont have any token
if (typeof request.data.token !== 'undefined') {
- url += `&token=${encodeURIComponent(JSON.stringify(request.data.token))}`
+ url += `&${URLNS}token=${encodeURIComponent(JSON.stringify(request.data.token))}`
}
for (let key in request.data) {
@@ -286,17 +291,17 @@ export class Messaging {
// no sense to send issuer config. Outlet() use own config,
// it can be dangerous if Outlet beleive to external config from URL HASH
- if (key === 'issuer') continue
+ if (key === 'issuer' || key === 'token') continue
if (!value) continue
if (value instanceof Array || value instanceof Object) {
- url += `&${key}=${JSON.stringify(value)}`
+ url += `&${URLNS}${key}=${JSON.stringify(value)}`
} else {
if (key === 'urlParams') {
url += `&${value}`
} else {
- url += `&${key}=${value}`
+ url += `&${URLNS}${key}=${value}`
}
}
}
diff --git a/src/outlet/__tests__/outlet.spec.ts b/src/outlet/__tests__/outlet.spec.ts
index 7e4e35e4..86e621db 100644
--- a/src/outlet/__tests__/outlet.spec.ts
+++ b/src/outlet/__tests__/outlet.spec.ts
@@ -1,17 +1,58 @@
import { Outlet } from '../index'
-
/*
TODO: Find a solution for TypeError: Cannot convert a BigInt value to a number at Math.pow ()
Thrown by import {Authenticator} from '@tokenscript/attestation' due to @tokenscript/attestation/src/libs/Point.ts
*/
-describe('Outlet spec', () => {
- test('placeholder test', () => {
+// @ts-ignore
+const outlet = new Outlet({
+ collectionID: 'devcon',
+ title: 'Devcon',
+ tokenOrigin: 'http://localhost:3002/',
+ attestationOrigin: 'https://test.attestation.id/',
+ base64senderPublicKeys: {
+ '6': 'MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABGMxHraqggr2keTXszIcchTjYjH5WXpDaBOYgXva82mKcGnKgGRORXSmcjWN2suUCMkLQj3UNlZCFWF10wIrrlw=',
+ },
+ base64attestorPubKey:
+ 'MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=',
+})
+
+describe('Test magic link token merging', () => {
+ // TicketId: 1235; Email 1
+ const ticket1 = {
+ token:
+ 'MIGTME0MATYCAgTTAgEBBEEELP0-vySEqJgldcvPSwL5VH_Bjta--7eHQafKwrwvH5MaPlOxSUqRtPf4GLs73BRJy-shtne8OV5C2e4-6qf5ggNCAEBVl-qTKHcKDKyjNszQ2NOOHS78dp56zQ6kWxxP2R4bE8-ZQs_zfTUS_HcNaLvyrsiil9ihndmcYD32Traiyscc',
+ id: '1',
+ secret: '1',
+ magic_link: '',
+ }
+ // TicketId: 1235; Email 2
+ const ticket2 = {
+ token:
+ 'MIGTME0MATYCAgTTAgEBBEEEDjlqyo764Y_sfQFUVOLrk6BN6RB7NDJuoxNL07aa13UPBrUh9mgF0Itge2EolzZkCXIN0mlHo0PhTIHhWMsOIwNCAIGsxXnRlAZb8ihGQo5CXnq1Gxui7ELcM0gKfR8EDnmNdGOGNLxjJdAuLRPyUuek9uuShQbkR-acpKsmlW86g_kb',
+ id: '2',
+ secret: '1',
+ magic_link: '',
+ }
+ // TicketId: 1236; Email 1
+ const ticket3 = {
+ token:
+ 'MIGTME0MATYCAgTUAgECBEEELP0-vySEqJgldcvPSwL5VH_Bjta--7eHQafKwrwvH5MaPlOxSUqRtPf4GLs73BRJy-shtne8OV5C2e4-6qf5ggNCAHJ4nMSLoDSshkso5w1MDup5fWBIt1mCByU2HVf8uu_5eIhXM6fpkQq3Uyr7m0VxcYK3eehaaPLOqnPId9teDYIb',
+ id: '1',
+ secret: '1',
+ magic_link: '',
+ }
+
+ test('Expect new attestation to be added', () => {
+ const tokens = outlet.mergeNewToken(ticket3, [ticket1]) as any[]
+ expect(tokens.length).toBe(2)
+ })
+ test('Expect existing attestation with same ID to be overwritten', () => {
+ const tokens = outlet.mergeNewToken(ticket2, [ticket1]) as any[]
expect(true).toBe(true)
})
-
- // test('Create outlet from token name', () => {
- // const outlet = new Outlet({ config: {tokenName: 'devcon-remote'}});
- // console.log(outlet);
- // });
+ test('Expect duplicate attestation to be skipped', () => {
+ const tokens = outlet.mergeNewToken(ticket1, [ticket1])
+ expect(tokens).toBe(false)
+ })
})
diff --git a/src/outlet/auth-handler.ts b/src/outlet/auth-handler.ts
index 4d8c7ad5..1cb5d58b 100644
--- a/src/outlet/auth-handler.ts
+++ b/src/outlet/auth-handler.ts
@@ -1,10 +1,10 @@
// @ts-nocheck
-import { ResponseActionBase } from '../core/messaging'
+import { ResponseActionBase, URLNS } from '../core/messaging'
import { OutletAction } from '../client/messaging'
import { Outlet, OutletInterface } from './index'
import { Authenticator } from '@tokenscript/attestation'
-import { logger } from '../utils'
+import { logger, removeUrlSearchParams } from '../utils'
import { isBrave, isMacOrIOS } from '../utils/support/getBrowserData'
export interface DevconToken {
@@ -241,18 +241,28 @@ export class AuthHandler {
if (this.redirectUrl) {
const curParams = new URLSearchParams(document.location.hash.substring(1))
+ // Request parameters for attestation.id
const params = new URLSearchParams()
- params.set('action', OutletAction.EMAIL_ATTEST_CALLBACK)
params.set('email', this.email)
params.set('address', this.address)
params.set('wallet', this.wallet)
- params.set('issuer', this.tokenDef.collectionID)
- params.set('token', JSON.stringify(this.unsignedToken))
- params.set('email-attestation-callback', this.redirectUrl)
- const requestor = curParams.get('requestor')
+ // Add extra params that will be required for the Attestation.id -> Outlet callback URL
+ const callbackUrl = new URL(this.redirectUrl)
+ const callbackParams = removeUrlSearchParams(new URLSearchParams(callbackUrl.hash.substring(1)))
+ callbackParams.set(URLNS + 'action', OutletAction.EMAIL_ATTEST_CALLBACK)
+ callbackParams.set(URLNS + 'issuer', this.tokenDef.collectionID)
+ callbackParams.set(URLNS + 'token', JSON.stringify(this.unsignedToken))
+
+ // Outlet -> Client callback
+ const requestor = curParams.get(URLNS + 'requestor')
+ if (requestor) {
+ callbackParams.set(URLNS + 'requestor', requestor)
+ }
+
+ callbackUrl.hash = callbackParams.toString()
- if (requestor) params.set('requestor', requestor)
+ params.set('email-attestation-callback', callbackUrl.href)
const goto = `${this.attestationOrigin}#${params.toString()}`
logger(2, 'authenticate. go to: ', goto)
@@ -294,7 +304,7 @@ export class AuthHandler {
iframe.src = this.attestationOrigin ?? ''
iframe.style.width = '800px'
- iframe.style.height = '700px'
+ iframe.style.height = '800px'
iframe.style.maxWidth = '100%'
iframe.style.background = '#fff'
@@ -302,7 +312,7 @@ export class AuthHandler {
this.iframeWrap = iframeWrap
iframeWrap.setAttribute(
'style',
- 'width:100%;min-height: 100vh; position: fixed; align-items: center; justify-content: center;display: none;top: 0; left: 0; background: #fffa',
+ 'width:101%;min-height: 100vh; position: fixed; align-items: center; justify-content: center;display: none;top: 0; left: 0; background: #fffa',
)
iframeWrap.appendChild(iframe)
diff --git a/src/outlet/index.ts b/src/outlet/index.ts
index c1e8f4ca..943474c6 100644
--- a/src/outlet/index.ts
+++ b/src/outlet/index.ts
@@ -1,11 +1,21 @@
-import { rawTokenCheck, readMagicUrl, storeMagicURL, decodeTokens, filterTokens } from '../core'
-import { logger, requiredParams, uint8toBuffer } from '../utils'
+import {
+ rawTokenCheck,
+ readTokenFromMagicUrl,
+ storeMagicURL,
+ decodeTokens,
+ decodeToken,
+ filterTokens,
+ readTokens,
+ OffChainTokenData,
+ DecodedToken,
+} from '../core'
+import { logger, requiredParams, uint8toBuffer, removeUrlSearchParams } from '../utils'
import { OutletAction, OutletResponseAction } from '../client/messaging'
import { AuthHandler } from './auth-handler'
// requred for default TicketDecoder.
import { SignedDevconTicket } from '@tokenscript/attestation/dist/asn1/shemas/SignedDevconTicket'
import { AsnParser } from '@peculiar/asn1-schema'
-import { ResponseActionBase, ResponseInterfaceBase } from '../core/messaging'
+import { ResponseActionBase, ResponseInterfaceBase, URLNS } from '../core/messaging'
export interface OutletInterface {
collectionID: string
@@ -90,9 +100,9 @@ export class Outlet {
}
}
- getDataFromQuery(itemKey: any): string {
- const val = this.urlParams ? this.urlParams.get(itemKey) : ''
- return val ? val : ''
+ getDataFromQuery(itemKey: string, namespaced = true): string {
+ itemKey = (namespaced ? URLNS : '') + itemKey
+ return this.urlParams ? this.urlParams.get(itemKey) : ''
}
getFilter() {
@@ -134,8 +144,10 @@ export class Outlet {
try {
const tokenString = this.getDataFromQuery('token')
let token = JSON.parse(tokenString)
- const attestationBlob = this.getDataFromQuery('attestation')
- const attestationSecret = '0x' + this.getDataFromQuery('requestSecret')
+
+ // Note: these params come from attestation.id and are not namespaced
+ const attestationBlob = this.getDataFromQuery('attestation', false)
+ const attestationSecret = '0x' + this.getDataFromQuery('requestSecret', false)
let authHandler = new AuthHandler(
this,
@@ -153,21 +165,24 @@ export class Outlet {
// re-direct back to origin
if (requesterURL) {
const params = new URLSearchParams(requesterURL.hash.substring(1))
- params.set('action', 'proof-callback')
- params.set('issuer', issuer)
- params.set('attestation', useToken as string)
+ params.set(URLNS + 'action', 'proof-callback')
+ params.set(URLNS + 'issuer', issuer)
+ params.set(URLNS + 'attestation', useToken as string)
+
+ // TODO: Remove once https://github.com/AlphaWallet/attestation.id/pull/196 is merged
+ params.delete('email')
+ params.delete('#email')
// add tokens to avoid redirect loop
// when use redirect to get tokens
-
let outlet = new Outlet(this.tokenConfig, true)
let issuerTokens = outlet.prepareTokenOutput({})
logger(2, 'issuerTokens: ', issuerTokens)
- params.set('tokens', JSON.stringify(issuerTokens))
+ params.set(URLNS + 'tokens', JSON.stringify(issuerTokens))
- requesterURL.hash = '#' + params.toString()
+ requesterURL.hash = params.toString()
console.log('urlToRedirect from OutletAction.EMAIL_ATTEST_CALLBACK: ', requesterURL.href)
@@ -184,7 +199,12 @@ export class Outlet {
this.dispatchAuthCallbackEvent(issuer, null, e.message)
}
- document.location.hash = ''
+ document.location.hash = removeUrlSearchParams(this.urlParams, [
+ 'attestation',
+ 'requestSecret',
+ 'address',
+ 'wallet',
+ ]).toString()
break
}
@@ -201,14 +221,16 @@ export class Outlet {
// store local storage item that can be later used to check if third party cookies are allowed.
// Note: This test can only be performed when the localstorage / cookie is assigned, then later requested.
/* localStorage.setItem("cookie-support-check", "test");
- this.sendCookieCheck(evtid);*/
+ this.sendCookieCheck(evtid);*/
+ // TODO: Remove singleUse - this is only needed in negotiator that calls readMagicLink.
+ // move single link somewhere that it can be used by both Outlet & LocalOutlet
if (!this.singleUse) {
+ await this.whitelistCheck(evtid, 'write')
await this.readMagicLink()
+ this.sendTokens(evtid)
}
- this.sendTokens(evtid)
-
break
}
}
@@ -219,16 +241,17 @@ export class Outlet {
}
public async readMagicLink() {
- const evtid = this.getDataFromQuery('evtid')
-
const { tokenUrlName, tokenSecretName, tokenIdName, itemStorageKey } = this.tokenConfig
try {
- const tokens = readMagicUrl(tokenUrlName, tokenSecretName, tokenIdName, itemStorageKey, this.urlParams)
+ const newToken = readTokenFromMagicUrl(tokenUrlName, tokenSecretName, tokenIdName, this.urlParams)
+ let tokensOutput = readTokens(itemStorageKey)
- await this.whitelistCheck(evtid, 'write')
+ const newTokens = this.mergeNewToken(newToken, tokensOutput.tokens)
- storeMagicURL(tokens, itemStorageKey)
+ if (newTokens !== false) {
+ storeMagicURL(newTokens, itemStorageKey)
+ }
const event = new Event('tokensupdated')
@@ -240,6 +263,56 @@ export class Outlet {
}
}
+ /**
+ * Merges a new magic link into the existing token data. If a token is found with the same ID it is overwritten.
+ * @private
+ * @returns false when no changes to the data are required - the token is already added
+ */
+ public mergeNewToken(newToken: OffChainTokenData, existingTokens: OffChainTokenData[]): OffChainTokenData[] | false {
+ const decodedNewToken = decodeToken(
+ newToken,
+ this.tokenConfig.tokenParser,
+ this.tokenConfig.unsignedTokenDataName,
+ false,
+ )
+
+ const newTokenId = this.getUniqueTokenId(decodedNewToken)
+
+ for (const [index, tokenData] of existingTokens.entries()) {
+ // Nothing required, this token already exists
+ if (tokenData.token === newToken.token) {
+ return false
+ }
+
+ const decodedTokenData = decodeToken(
+ tokenData,
+ this.tokenConfig.tokenParser,
+ this.tokenConfig.unsignedTokenDataName,
+ false,
+ )
+
+ const tokenId = this.getUniqueTokenId(decodedTokenData)
+
+ // Overwrite existing token
+ if (newTokenId === tokenId) {
+ existingTokens[index] = newToken
+ return existingTokens
+ }
+ }
+
+ // Add as new token
+ existingTokens.push(newToken)
+ return existingTokens
+ }
+
+ /**
+ * Calculates a unique token ID to identify this ticket. Tickets can be reissued and have a different commitment, but are still the same token
+ * @private
+ */
+ private getUniqueTokenId(decodedToken: DecodedToken) {
+ return `${decodedToken.devconId}-${decodedToken.ticketIdNumber ?? decodedToken.ticketIdString}`
+ }
+
private dispatchAuthCallbackEvent(issuer: string, proof?: string, error?: string) {
const event = new CustomEvent('auth-callback', {
detail: {
@@ -371,6 +444,7 @@ export class Outlet {
includeSigned,
)
+ // remove duplicates check
return filterTokens(decodedTokens, filter)
}
@@ -379,10 +453,7 @@ export class Outlet {
const unsignedToken = JSON.parse(token)
- const redirect =
- this.urlParams.get('redirect') === 'true'
- ? document.location.origin + document.location.pathname + document.location.search
- : false
+ const redirect = this.getDataFromQuery('redirect') === 'true' ? document.location.href : false
try {
// check if token issuer
@@ -430,6 +501,7 @@ export class Outlet {
}
}
+ // TODO: Consolidate redirect callback for tokens, proof & errors into the sendMessageResponse function to remove duplication
private sendTokens(evtid: any) {
let issuerTokens = this.prepareTokenOutput(this.getFilter())
@@ -440,9 +512,9 @@ export class Outlet {
let url = this.redirectCallbackUrl
const params = new URLSearchParams(url.hash.substring(1))
- params.set('action', OutletAction.GET_ISSUER_TOKENS + '-response')
- params.set('issuer', this.tokenConfig.collectionID)
- params.set('tokens', JSON.stringify(issuerTokens))
+ params.set(URLNS + 'action', OutletAction.GET_ISSUER_TOKENS + '-response')
+ params.set(URLNS + 'issuer', this.tokenConfig.collectionID)
+ params.set(URLNS + 'tokens', JSON.stringify(issuerTokens))
url.hash = '#' + params.toString()
@@ -473,9 +545,9 @@ export class Outlet {
let url = this.redirectCallbackUrl
const params = new URLSearchParams(url.hash.substring(1))
- params.set('action', ResponseActionBase.ERROR)
- params.set('issuer', issuer)
- params.set('error', error)
+ params.set(URLNS + 'action', ResponseActionBase.ERROR)
+ params.set(URLNS + 'issuer', issuer)
+ params.set(URLNS + 'error', error)
console.log('Redirecting error: ', error)
@@ -496,9 +568,9 @@ export class Outlet {
const requesterURL = this.redirectCallbackUrl.href
const params = new URLSearchParams()
- params.set('action', 'proof-callback')
- params.set('issuer', issuer)
- params.set('error', error)
+ params.set(URLNS + 'action', 'proof-callback')
+ params.set(URLNS + 'issuer', issuer)
+ params.set(URLNS + 'error', error)
document.location.href = requesterURL + '#' + params.toString()
}
diff --git a/src/theme/common.scss b/src/theme/common.scss
index e37bad66..e851f3a1 100644
--- a/src/theme/common.scss
+++ b/src/theme/common.scss
@@ -85,6 +85,13 @@
font-size: max(1rem, 16px);
}
+.overlay-tn .wallet-button-tn img,
+.overlay-tn .wallet-button-tn svg {
+ max-width: 100%;
+ width: 62px;
+ height: auto;
+}
+
.overlay-tn .no-tokens-tn {
padding: 0 1rem;
}
@@ -579,7 +586,7 @@ li.issuer-connect-banner-tn .fungible-token-btn {
display: flex;
position: relative;
left: 0;
- transition: all 0.2s ease-out;
+ transition: all 0.25s ease-out;
height: 100%;
}
@@ -691,6 +698,107 @@ li.issuer-connect-banner-tn .fungible-token-btn {
.view-content-tn {
height: 100%;
+ flex: 50%;
+ position: relative;
+ width: 100%;
+}
+
+.transition-wrapper-tn {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+
+ &.slide-in-left-tn,
+ &.slide-in-right-tn {
+ width: 200% !important;
+ }
+
+ &.slide-in-left-tn {
+ right: 0;
+ &.slide-in {
+ animation: slide-in-left 0.25s forwards ease-out;
+ -webkit-animation: slide-in-left 0.25s forwards ease-out;
+ }
+ }
+
+ &.slide-in-right-tn {
+ left: 0;
+ &.slide-in {
+ animation: slide-in-right 0.25s forwards ease-out;
+ -webkit-animation: slide-in-right 0.25s forwards ease-out;
+ }
+ }
+
+ &.slide-in-top-tn,
+ &.slide-in-bottom-tn {
+ height: 200% !important;
+ flex-direction: column;
+ }
+
+ &.slide-in-top-tn {
+ bottom: 0;
+ &.slide-in {
+ animation: slide-in-top 0.25s forwards ease-out;
+ -webkit-animation: slide-in-top 0.25s forwards ease-out;
+ }
+ }
+
+ &.slide-in-bottom-tn {
+ top: 0;
+ &.slide-in {
+ animation: slide-in-bottom 0.25s forwards ease-out;
+ -webkit-animation: slide-in-bottom 0.25s forwards ease-out;
+ }
+ }
+}
+
+@keyframes slide-in-left {
+ 100% {
+ transform: translateX(50%);
+ }
+}
+
+@-webkit-keyframes slide-in-left {
+ 100% {
+ -webkit-transform: translateX(50%);
+ }
+}
+
+@keyframes slide-in-right {
+ 100% {
+ transform: translateX(-50%);
+ }
+}
+
+@-webkit-keyframes slide-in-right {
+ 100% {
+ -webkit-transform: translateX(-50%);
+ }
+}
+
+@keyframes slide-in-top {
+ 100% {
+ transform: translateY(50%);
+ }
+}
+
+@-webkit-keyframes slide-in-top {
+ 100% {
+ -webkit-transform: translateY(50%);
+ }
+}
+
+@keyframes slide-in-bottom {
+ 100% {
+ transform: translateY(-50%);
+ }
+}
+
+@-webkit-keyframes slide-in-bottom {
+ 100% {
+ -webkit-transform: translateY(-50%);
+ }
}
.scroll-tn {
diff --git a/src/utils/__tests__/util.spec.ts b/src/utils/__tests__/util.spec.ts
index 048cae7d..60e81e2a 100644
--- a/src/utils/__tests__/util.spec.ts
+++ b/src/utils/__tests__/util.spec.ts
@@ -2,7 +2,14 @@
window.DISPLAY_DEBUG_LEVEL = 1
import { hasUncaughtExceptionCaptureCallback } from 'process'
-import { logger, requiredParams, compareObjects, base64ToUint8array, waitForElementToExist } from './../index'
+import {
+ logger,
+ requiredParams,
+ compareObjects,
+ base64ToUint8array,
+ waitForElementToExist,
+ removeUrlSearchParams,
+} from './../index'
import { errorHandler } from '../index'
// TODO: add unit tests for the following functions:
@@ -90,3 +97,21 @@ describe('util Spec errorHandler', () => {
expect(errorHandler(err, 'error', null, null, false, false)).toEqual({ message: err, data: null, type: 'error' })
})
})
+
+describe('util Spec removeUrlSearchParams', () => {
+ test('Expect no parameters to be left', () => {
+ let params = new URLSearchParams('tn-action=get-tokens&tn-issuer=devcon')
+ params = removeUrlSearchParams(params)
+ expect(params.toString()).toEqual('')
+ })
+ test('Expect non-namespaced parameters to be removed', () => {
+ let params = new URLSearchParams('tn-action=get-tokens&tn-issuer=devcon&email=test@test.com')
+ params = removeUrlSearchParams(params, ['email'])
+ expect(params.toString()).toEqual('')
+ })
+ test('Expect non-specified parameter to be retained', () => {
+ let params = new URLSearchParams('tn-action=get-tokens&tn-issuer=devcon&email=test@test.com&redirectMode=always')
+ params = removeUrlSearchParams(params, ['email'])
+ expect(params.toString()).toEqual('redirectMode=always')
+ })
+})
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 8483330f..315ca36f 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,4 +1,5 @@
import { Buffer } from 'buffer'
+import { URLNS } from '../core/messaging'
declare global {
interface Window {
@@ -142,3 +143,27 @@ export const tokenRequest = async (query: string, silenceRequestError: boolean)
throw new Error(`HTTP error.`)
}
}
+
+/**
+ * Remove callback parameters from the URL by providing a list of keys or a namespace (key prefix)
+ * @param params
+ * @param additionalParams
+ * @param namespace
+ */
+export const removeUrlSearchParams = (
+ params: URLSearchParams,
+ additionalParams: string[] = [],
+ namespace: string | null = URLNS,
+) => {
+ if (namespace)
+ for (let key of Array.from(params.keys())) {
+ // Iterator needs to be converted to array since we are deleting keys
+ if (key.indexOf(namespace) === 0) params.delete(key)
+ }
+
+ for (let paramName of additionalParams) {
+ if (params.has(paramName)) params.delete(paramName)
+ }
+
+ return params
+}
diff --git a/src/utils/support/__tests__/getBrowswerData.spec.ts b/src/utils/support/__tests__/getBrowswerData.spec.ts
index 51f7f5fd..0b44900b 100644
--- a/src/utils/support/__tests__/getBrowswerData.spec.ts
+++ b/src/utils/support/__tests__/getBrowswerData.spec.ts
@@ -17,10 +17,12 @@ describe('browser simulations', () => {
iE9: false,
edge: false,
chrome: true,
+ desktop: true,
phantomJS: false,
fireFox: false,
safari: false,
android: false,
+ anyMetamask: false,
iOS: false,
mac: true,
windows: false,
diff --git a/src/utils/support/getBrowserData.ts b/src/utils/support/getBrowserData.ts
index a0da6c4d..45e33ae8 100644
--- a/src/utils/support/getBrowserData.ts
+++ b/src/utils/support/getBrowserData.ts
@@ -37,6 +37,8 @@ export const getBrowserData = () => {
isTrust: false,
isStatusWallet: false,
isGoWallet: false,
+ // this prop is TRUE when Metamask disabled or not installed
+ isBraveWallet: false,
}
}
@@ -61,8 +63,10 @@ export const getBrowserData = () => {
iOS: isIOS,
mac: isMac,
windows: isWindows,
+ desktop: navigator.userAgent?.indexOf("Mobi") === -1,
touchDevice: isTouchDevice,
metaMask: isMetaMask,
+ anyMetamask: !!windowEthereum.isMetaMask && !isTrust && !windowEthereum.isBraveWallet,
alphaWallet: isAlphaWallet,
mew: isMyEthereumWallet,
trust: isTrust,
diff --git a/src/version.ts b/src/version.ts
index dd064dfd..6a070dee 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1,2 +1,2 @@
// modified by build process.
-export const VERSION = '2.3.0'
+export const VERSION = '2.4.0'
diff --git a/src/wallet/FlowProvider.ts b/src/wallet/FlowProvider.ts
index 04c7f3eb..985a3d43 100644
--- a/src/wallet/FlowProvider.ts
+++ b/src/wallet/FlowProvider.ts
@@ -1,4 +1,4 @@
-import * as fcl from '@onflow/fcl'
+import * as fcl from '@onflow/fcl/dist/fcl.umd.min'
// const unsubscribe = fcl.currentUser.subscribe(currentUser => {
// console.log("The Current User", currentUser)
diff --git a/src/wallet/WalletConnectProvider.ts b/src/wallet/WalletConnectProvider.ts
index c2fabf2f..efc4697d 100644
--- a/src/wallet/WalletConnectProvider.ts
+++ b/src/wallet/WalletConnectProvider.ts
@@ -1,24 +1,10 @@
import WalletConnectProvider from '@walletconnect/web3-provider/dist/umd/index.min'
+import { WC_DEFAULT_RPC_MAP } from './WalletConnectV2Provider'
export const getWalletConnectProviderInstance = async (checkConnectionOnly?: boolean) => {
return new WalletConnectProvider({
infuraId: '7753fa7b79d2469f97c156780fce37ac',
qrcode: !checkConnectionOnly,
- rpc: {
- 5: 'https://eth-goerli.g.alchemy.com/v2/yVhq9zPJorAWsw-F87fEabSUl7cCU6z4', // Goerli
- 11155111: 'https://sepolia.infura.io/v3/9f79b2f9274344af90b8d4e244b580ef', // Sepolia
- 137: 'https://polygon-rpc.com/', // Polygon
- 80001: 'https://polygon-mumbai.g.alchemy.com/v2/rVI6pOV4irVsrw20cJxc1fxK_1cSeiY0', // mumbai
- 56: 'https://bsc-dataseed.binance.org/', // BSC,
- 97: 'https://data-seed-prebsc-1-s1.binance.org:8545', // BSC testnet
- 43114: 'https://api.avax.network/ext/bc/C/rpc', // Avalanche
- 43113: 'https://api.avax-test.network/ext/bc/C/rpc', // Fuji testnet
- 250: 'https://rpc.fantom.network/', // Fantom,
- 25: 'https://evm-cronos.crypto.org', // Cronos,
- 338: 'https://evm-t3.cronos.org', // Cronos testnet(Rinkeby)
- 42161: 'https://arb1.arbitrum.io/rpc', // Arbitrum
- 421613: 'https://arb-goerli.g.alchemy.com/v2/nFrflomLgsQQL5NWjGileAVqIGGxZWce', // Arbitrum goerli,
- 10: 'https://mainnet.optimism.io',
- },
+ rpc: WC_DEFAULT_RPC_MAP,
})
}
diff --git a/src/wallet/WalletConnectV2Provider.ts b/src/wallet/WalletConnectV2Provider.ts
index 99da73b1..0d5a7f58 100644
--- a/src/wallet/WalletConnectV2Provider.ts
+++ b/src/wallet/WalletConnectV2Provider.ts
@@ -1,25 +1,24 @@
import UniversalProvider from '@walletconnect/universal-provider/dist/index.umd'
-export const CUSTOM_RPCS_FOR_WC_V2 = {
+export const WC_DEFAULT_RPC_MAP = {
1: 'https://ethereum.publicnode.com', // mainnet
5: 'https://eth-goerli.g.alchemy.com/v2/yVhq9zPJorAWsw-F87fEabSUl7cCU6z4', // Goerli
- // 11155111: 'https://sepolia.infura.io/v3/9f79b2f9274344af90b8d4e244b580ef', // Sepolia
+ 11155111: 'https://sepolia.infura.io/v3/9f79b2f9274344af90b8d4e244b580ef', // Sepolia
137: 'https://polygon-rpc.com/', // Polygon
80001: 'https://polygon-mumbai.g.alchemy.com/v2/rVI6pOV4irVsrw20cJxc1fxK_1cSeiY0', // mumbai
56: 'https://bsc-dataseed.binance.org/', // BSC,
- // 97: 'https://data-seed-prebsc-1-s1.binance.org:8545', // BSC testnet
+ 97: 'https://data-seed-prebsc-1-s1.binance.org:8545', // BSC testnet
43114: 'https://api.avax.network/ext/bc/C/rpc', // Avalanche
- // 43113: 'https://api.avax-test.network/ext/bc/C/rpc', // Fuji testnet
+ 43113: 'https://api.avax-test.network/ext/bc/C/rpc', // Fuji testnet
250: 'https://rpc.fantom.network/', // Fantom,
25: 'https://evm-cronos.crypto.org', // Cronos,
- // 338: 'https://evm-t3.cronos.org', // Cronos testnet(Rinkeby)
+ 338: 'https://evm-t3.cronos.org', // Cronos testnet
42161: 'https://arb1.arbitrum.io/rpc', // Arbitrum
- // 421613: 'https://arb-goerli.g.alchemy.com/v2/nFrflomLgsQQL5NWjGileAVqIGGxZWce', // Arbitrum goerli,
+ 421613: 'https://arb-goerli.g.alchemy.com/v2/nFrflomLgsQQL5NWjGileAVqIGGxZWce', // Arbitrum goerli,
10: 'https://mainnet.optimism.io', // Optimism
}
-// https://ethereum.publicnode.com
-export const WC_V2_CHAINS = [
+export const WC_V2_DEFAULT_CHAINS = [
'eip155:1', // Mainnet
// 'eip155:5',
// 'eip155:11155111',
diff --git a/src/wallet/Web3WalletProvider.ts b/src/wallet/Web3WalletProvider.ts
index 92799872..c443cc6c 100644
--- a/src/wallet/Web3WalletProvider.ts
+++ b/src/wallet/Web3WalletProvider.ts
@@ -2,6 +2,7 @@ import { ethers } from 'ethers'
import { logger } from '../utils'
import { SafeConnectOptions } from './SafeConnectProvider'
import { Client } from '../client'
+import { WalletOptionsInterface } from '../client/interface'
interface WalletConnectionState {
[index: string]: WalletConnection
@@ -21,13 +22,11 @@ export class Web3WalletProvider {
connections: WalletConnectionState = {}
- safeConnectOptions?: SafeConnectOptions
- client: Client
-
- constructor(client: Client, safeConnectOptions?: SafeConnectOptions) {
- this.client = client
- this.safeConnectOptions = safeConnectOptions
- }
+ constructor(
+ private client: Client,
+ private walletOptions?: WalletOptionsInterface,
+ public safeConnectOptions?: SafeConnectOptions,
+ ) {}
saveConnections() {
let savedConnections: WalletConnectionState = {}
@@ -40,7 +39,6 @@ export class Web3WalletProvider {
chainId: con.chainId,
providerType: con.providerType,
blockchain: con.blockchain,
- ethers: ethers,
}
}
@@ -273,12 +271,9 @@ export class Web3WalletProvider {
namespaces: {
eip155: {
methods: ['eth_sendTransaction', 'eth_signTransaction', 'eth_sign', 'personal_sign', 'eth_signTypedData'],
- chains: walletConnectProvider.WC_V2_CHAINS,
+ chains: this.walletOptions?.walletConnectV2?.chains ?? walletConnectProvider.WC_V2_DEFAULT_CHAINS,
events: ['chainChanged', 'accountsChanged'],
- rpcMap: walletConnectProvider.CUSTOM_RPCS_FOR_WC_V2,
- // rpcMap: {
- // 1: `https://mainnet.infura.io/v3/9f79b2f9274344af90b8d4e244b580ef`
- // }
+ rpcMap: this.walletOptions?.walletConnectV2?.rpcMap ?? walletConnectProvider.WC_DEFAULT_RPC_MAP,
},
},
pairingTopic: pairing?.topic,
@@ -315,7 +310,7 @@ export class Web3WalletProvider {
return this.registerProvider(provider, 'Torus')
}
- async Phantom() {
+ async Phantom(checkConnectionOnly: boolean) {
logger(2, 'connect Phantom')
if (typeof window.solana !== 'undefined') {
@@ -326,11 +321,11 @@ export class Web3WalletProvider {
// mainnet-beta,
return this.registerNewWalletAddress(accountAddress, 'mainnet-beta', 'phantom', window.solana, 'solana')
} else {
- throw new Error('MetaMask is not available. Please check the extension is supported and active.')
+ throw new Error('Phantom is not available. Please check the extension is supported and active.')
}
}
- async SafeConnect() {
+ async SafeConnect(checkConnectionOnly: boolean) {
logger(2, 'connect SafeConnect')
const provider = await this.getSafeConnectProvider()
@@ -342,35 +337,18 @@ export class Web3WalletProvider {
return address
}
- async flowSubscribe(fcl, currentUser) {
- try {
- if (currentUser.addr) {
- this.registerNewWalletAddress(currentUser.addr, 1, 'flow', fcl)
+ async Flow(checkConnectionOnly: boolean) {
+ const flowProvider = await import('./FlowProvider')
+ const fcl = flowProvider.getFlowProvider()
- const ui = this.client.getUi()
+ await fcl.currentUser.authenticate()
+ let currentUser = await fcl.currentUser.snapshot()
- if (ui) ui.dismissLoader()
+ // No user address after authenticate() then connect was unsuccesfull
+ if (!currentUser.addr) throw new Error('Failed to connect Flow wallet')
- this.client.enrichTokenLookupDataOnChainTokens()
- if (ui) ui.updateUI('main')
- }
- } catch (e) {
- console.error('flow wallet connection error ==>', e)
- this.client.getUi().showError('Flow wallet connection error.')
- }
- }
-
- async Flow() {
- try {
- const flowProvider = await import('./FlowProvider')
- const fcl = flowProvider.getFlowProvider()
-
- fcl.currentUser.subscribe((currentUser) => this.flowSubscribe(fcl, currentUser))
- fcl.authenticate()
- } catch (e) {
- console.error('error ==>', e)
- }
- return ''
+ this.registerNewWalletAddress(currentUser.addr, 1, 'flow', fcl)
+ return currentUser.addr
}
safeConnectAvailable() {
diff --git a/src/wallet/__test__/wallet.spec.ts b/src/wallet/__test__/wallet.spec.ts
index c06dcd34..5648a2ec 100644
--- a/src/wallet/__test__/wallet.spec.ts
+++ b/src/wallet/__test__/wallet.spec.ts
@@ -1,4 +1,7 @@
/* eslint-disable no-mixed-spaces-and-tabs */
+import { TextDecoder, TextEncoder } from 'text-encoding'
+global.TextEncoder = TextEncoder
+global.TextDecoder = TextDecoder
import { Client } from '../../client/index'
import { SafeConnectOptions, SafeConnectProvider } from '../SafeConnectProvider'
import { Web3WalletProvider } from '../Web3WalletProvider'
@@ -29,17 +32,12 @@ let tokenNegotiatorClient = new Client({
],
})
-let safeConnectOptions: SafeConnectOptions = {
- url: 'https://safeconnect.tokenscript.org',
- initialProof: 'address_attest',
-}
-
describe('wallet spec', () => {
let web3WalletProvider: Web3WalletProvider
let safeConnectProvider: SafeConnectProvider
test('web3WalletProvider a new instance', () => {
- web3WalletProvider = new Web3WalletProvider(tokenNegotiatorClient, safeConnectOptions)
+ web3WalletProvider = new Web3WalletProvider(tokenNegotiatorClient, null, null)
expect(web3WalletProvider).toBeDefined()
})
diff --git a/webpack.config.js b/webpack.config.js
index 3bef58d9..5b0acf8e 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -16,6 +16,12 @@ module.exports = {
],
module: {
rules: [
+ {
+ test: /\.m?js$/,
+ resolve: {
+ fullySpecified: false
+ },
+ },
{
test: /\.tsx?$/,
exclude: /node_modules/,