Skip to content

Commit

Permalink
Merge pull request #2707 from tallycash/wallet-connect-init-2
Browse files Browse the repository at this point in the history
Wallet connect init 2
  • Loading branch information
Gergő Nagy authored Dec 5, 2022
2 parents b9abfe6 + 5eb8b89 commit 26f72cf
Show file tree
Hide file tree
Showing 7 changed files with 691 additions and 4 deletions.
1 change: 1 addition & 0 deletions .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ POSTHOG_URL="https://app.posthog.com/capture/"
POSTHOG_API_KEY="phc_VzveyNxrn2xyiKDYn7XjrgaqELGeUilDZGiBVh6jNmh"
USE_ANALYTICS_SOURCE="DEV"
POAP_API_KEY="Djq0jJrWaQCzzvvoTqy0BgFidzy4bugDX1mTY28EZpqcUDSNICLcVQnex2wY7D9t5QB2aQlPQRdw8GiOVcE5kgQywGiDraJDfd3GVxnDASm1PoRBjOBh9fOrgymNsMQc"
SUPPORT_WALLET_CONNECT=false
1 change: 1 addition & 0 deletions background/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const RuntimeFlag = {
SUPPORT_ACHIEVEMENTS_BANNER:
process.env.SUPPORT_ACHIEVEMENTS_BANNER === "true",
SUPPORT_NFT_TAB: process.env.SUPPORT_NFT_TAB === "true",
SUPPORT_WALLET_CONNECT: process.env.SUPPORT_WALLET_CONNECT === "true",
} as const

type BuildTimeFlagType = keyof typeof BuildTimeFlag
Expand Down
31 changes: 28 additions & 3 deletions background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import {
LedgerService,
SigningService,
NFTsService,
WalletConnectService,
AnalyticsService,
getNoopService,
} from "./services"

import { HexString, KeyringTypes } from "./types"
Expand Down Expand Up @@ -140,7 +143,6 @@ import { selectActivitesHashesForEnrichment } from "./redux-slices/selectors"
import { getActivityDetails } from "./redux-slices/utils/activities-utils"
import { getRelevantTransactionAddresses } from "./services/enrichment/utils"
import { AccountSignerWithId } from "./signing"
import AnalyticsService from "./services/analytics"
import { AnalyticsPreferences } from "./services/preferences/types"
import { isSmartContractFungibleAsset } from "./assets"
import { FeatureFlags, isEnabled } from "./features"
Expand Down Expand Up @@ -293,6 +295,14 @@ export default class Main extends BaseService<never> {

const nftsService = NFTsService.create(chainService)

const walletConnectService = isEnabled(FeatureFlags.SUPPORT_WALLET_CONNECT)
? WalletConnectService.create(
providerBridgeService,
internalEthereumProviderService,
preferenceService
)
: getNoopService<WalletConnectService>()

let savedReduxState = {}
// Setting READ_REDUX_CACHE to false will start the extension with an empty
// initial state, which can be useful for development
Expand Down Expand Up @@ -333,7 +343,8 @@ export default class Main extends BaseService<never> {
await ledgerService,
await signingService,
await analyticsService,
await nftsService
await nftsService,
await walletConnectService
)
}

Expand Down Expand Up @@ -415,7 +426,13 @@ export default class Main extends BaseService<never> {
* A promise to the NFTs service which takes care of NFTs data, fetching, updating
* details and prices of NFTs for imported accounts.
*/
private nftsService: NFTsService
private nftsService: NFTsService,

/**
* A promise to the Wallet Connect service which takes care of handling wallet connect
* protocol and communication.
*/
private walletConnectService: WalletConnectService
) {
super({
initialLoadWaitExpired: {
Expand Down Expand Up @@ -476,6 +493,7 @@ export default class Main extends BaseService<never> {
this.signingService.startService(),
this.analyticsService.startService(),
this.nftsService.startService(),
this.walletConnectService.startService(),
]

await Promise.all(servicesToBeStarted)
Expand All @@ -497,6 +515,7 @@ export default class Main extends BaseService<never> {
this.signingService.stopService(),
this.analyticsService.stopService(),
this.nftsService.stopService(),
this.walletConnectService.stopService(),
]

await Promise.all(servicesToBeStopped)
Expand All @@ -516,6 +535,7 @@ export default class Main extends BaseService<never> {
this.connectLedgerService()
this.connectSigningService()
this.connectAnalyticsService()
this.connectWalletConnectService()

// Nothing else beside creating a service should happen when feature flag is off
if (isEnabled(FeatureFlags.SUPPORT_NFT_TAB)) {
Expand Down Expand Up @@ -1444,6 +1464,11 @@ export default class Main extends BaseService<never> {
})
}

// eslint-disable-next-line class-methods-use-this
connectWalletConnectService(): void {
// TODO: here comes the glue between the UI and service layer
}

async getActivityDetails(txHash: string): Promise<ActivityDetail[]> {
const addressNetwork = this.store.getState().ui.selectedAccount
const transaction = await this.chainService.getTransaction(
Expand Down
3 changes: 3 additions & 0 deletions background/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
"@tallyho/window-provider": "0.0.1",
"@types/w3c-web-usb": "^1.0.5",
"@uniswap/token-lists": "^1.0.0-beta.30",
"@walletconnect/sign-client": "^2.1.4",
"@walletconnect/types": "^2.1.4",
"@walletconnect/utils": "^2.1.4",
"ajv": "^8.6.2",
"ajv-formats": "^2.1.0",
"assert": "^2.0.0",
Expand Down
12 changes: 12 additions & 0 deletions background/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Emittery from "emittery"

export type {
ServiceLifecycleEvents,
Service,
Expand All @@ -17,4 +19,14 @@ export { default as DoggoService } from "./doggo"
export { default as TelemetryService } from "./telemetry"
export { default as LedgerService } from "./ledger"
export { default as SigningService } from "./signing"
export { default as AnalyticsService } from "./analytics"
export { default as NFTsService } from "./nfts"
export { default as WalletConnectService } from "./wallet-connect"

export function getNoopService<T>(): T {
return Promise.resolve({
startService: () => Promise.resolve(),
stopService: () => Promise.resolve(),
emitter: new Emittery(),
}) as unknown as T
}
158 changes: 158 additions & 0 deletions background/services/wallet-connect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import SignClient from "@walletconnect/sign-client"
import { parseUri } from "@walletconnect/utils"
import { SignClientTypes, SessionTypes } from "@walletconnect/types"

import { ServiceCreatorFunction, ServiceLifecycleEvents } from "../types"

import BaseService from "../base"
import PreferenceService from "../preferences"
import ProviderBridgeService from "../provider-bridge"
import InternalEthereumProviderService from "../internal-ethereum-provider"

interface Events extends ServiceLifecycleEvents {
placeHolderEventForTypingPurposes: string
}

/*
* The walletconnect service is responsible for encapsulating the wallet connect
* implementation details, maintaining the websocket connection, handling the protocol
* requirements ant tying the communication with the rest of the extension codebase.
*
* NOTE: This is a boundary between a low trust and a high trust context just like the
* provider bridge service. Extra careful coding is required here, to make sure we check
* and sanitize the communication properly before it can reach the rest of the codebase.
*/
export default class WalletConnectService extends BaseService<Events> {
signClient: SignClient | undefined

/*
* Create a new WalletConnectService. The service isn't initialized until
* startService() is called and resolved.
*/
static create: ServiceCreatorFunction<
Events,
WalletConnectService,
[
Promise<ProviderBridgeService>,
Promise<InternalEthereumProviderService>,
Promise<PreferenceService>
]
> = async (
providerBridgeService,
internalEthereumProviderService,
preferenceService
) => {
return new this(
await providerBridgeService,
await internalEthereumProviderService,
await preferenceService
)
}

private constructor(
private providerBridgeService: ProviderBridgeService,
private internalEthereumProviderService: InternalEthereumProviderService,
private preferenceService: PreferenceService
) {
super()
}

protected override async internalStartService(): Promise<void> {
await super.internalStartService()

await this.initializeWalletConnect()
}

protected override async internalStopService(): Promise<void> {
// TODO: add this back, when we introduce a db to this service.
// this.db.close()

await super.internalStopService()
}

// eslint-disable-next-line class-methods-use-this
private async initializeWalletConnect() {
this.signClient = await WalletConnectService.createSignClient()
this.defineEventHandlers()

// TODO: remove this, inject uri
// simulate connection attempt
const wcUri = "wc:710f..."
await this.performConnection(wcUri)
}

private static createSignClient(): Promise<SignClient> {
return SignClient.init({
logger: "debug", // TODO: set from .env
projectId: "9ab2e13df08600b06ac588e1292d6512", // TODO: set from .env
relayUrl: "wss://relay.walletconnect.com",
metadata: {
// TODO: customize this metadata
name: "Tally Ho Wallet",
description: "WalletConnect for Tally Ho wallet",
url: "https://walletconnect.com/",
icons: ["https://avatars.githubusercontent.com/u/37784886"],
},
})
}

private defineEventHandlers(): void {
const address = "0xcd6b1f2080bde01d56023c9b50cd91ff09fefd73" // TODO: remove this, replace with real address

this.signClient?.on(
"session_proposal",
async (proposal: SignClientTypes.EventArguments["session_proposal"]) => {
// TODO: in case of a new connection, this callback should perform request processing AFTER wallet selection/confirmation dialog
const { id, params } = proposal
const { requiredNamespaces, relays } = params

// TODO: expand this section to be able to match requiredNamespaces to actual wallet
const key = "eip155"
const accounts = [`eip155:1:${address}`]
const namespaces: SessionTypes.Namespaces = {}
namespaces[key] = {
accounts,
methods: requiredNamespaces[key].methods,
events: requiredNamespaces[key].events,
}

if (this.signClient !== undefined && relays.length > 0) {
const { acknowledged } = await this.signClient.approve({
id,
relayProtocol: relays[0].protocol,
namespaces,
})
await acknowledged()
} else {
// TODO: how to handle this case?
}
}
)

this.signClient?.on(
"session_request",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(event: SignClientTypes.EventArguments["session_request"]) => {}
)
}

async performConnection(uri: string): Promise<void> {
if (this.signClient === undefined) {
return
}

try {
const { version } = parseUri(uri)

// Route the provided URI to the v1 SignClient if URI version indicates it, else use v2.
if (version === 1) {
// createLegacySignClient({ uri })
} else if (version === 2) {
await this.signClient.pair({ uri })
} else {
// TODO: decide how to handle this
}
// eslint-disable-next-line no-empty
} catch (err: unknown) {}
}
}
Loading

0 comments on commit 26f72cf

Please sign in to comment.