Skip to content

Commit

Permalink
Feat/wallet connect v2
Browse files Browse the repository at this point in the history
  • Loading branch information
jsamol authored and godenzim committed Jun 21, 2023
1 parent 0c98e9c commit eaa50a2
Show file tree
Hide file tree
Showing 17 changed files with 2,845 additions and 625 deletions.
5 changes: 3 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ module.exports = {
'@typescript-eslint/no-unused-vars': [
'error',
{
'argsIgnorePattern': '^_'
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_'
}
],
'@typescript-eslint/no-use-before-define': 'off',
Expand Down Expand Up @@ -137,7 +138,7 @@ module.exports = {
'new-parens': 'off',
'newline-per-chained-call': 'off',
'no-duplicate-case': 'error',
'no-duplicate-imports': 'error',
'no-duplicate-imports': 'warn',
'no-empty': 'off',
'no-extra-semi': 'off',
'no-invalid-this': 'error',
Expand Down
2,373 changes: 1,927 additions & 446 deletions package-lock.json

Large diffs are not rendered by default.

39 changes: 21 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,28 @@
}
},
"dependencies": {
"@airgap/aeternity": "0.13.17-beta.0",
"@airgap/aeternity": "0.13.17-beta.1",
"@airgap/angular-core": "0.0.39-beta.5",
"@airgap/angular-ngrx": "0.0.39-beta.5",
"@airgap/astar": "0.13.17-beta.0",
"@airgap/astar": "0.13.17-beta.1",
"@airgap/beacon-sdk": "3.1.5-beta.0",
"@airgap/bitcoin": "0.13.17-beta.0",
"@airgap/coinlib-core": "0.13.17-beta.0",
"@airgap/coreum": "0.13.17-beta.0",
"@airgap/cosmos": "0.13.17-beta.0",
"@airgap/cosmos-core": "0.13.17-beta.0",
"@airgap/crypto": "0.13.17-beta.0",
"@airgap/ethereum": "0.13.17-beta.0",
"@airgap/groestlcoin": "0.13.17-beta.0",
"@airgap/icp": "0.13.17-beta.0",
"@airgap/module-kit": "0.13.17-beta.0",
"@airgap/moonbeam": "0.13.17-beta.0",
"@airgap/optimism": "0.13.17-beta.0",
"@airgap/polkadot": "0.13.17-beta.0",
"@airgap/bitcoin": "0.13.17-beta.1",
"@airgap/coinlib-core": "0.13.17-beta.1",
"@airgap/coreum": "0.13.17-beta.1",
"@airgap/cosmos": "0.13.17-beta.1",
"@airgap/cosmos-core": "0.13.17-beta.1",
"@airgap/crypto": "0.13.17-beta.1",
"@airgap/ethereum": "0.13.17-beta.1",
"@airgap/groestlcoin": "0.13.17-beta.1",
"@airgap/icp": "0.13.17-beta.1",
"@airgap/module-kit": "0.13.17-beta.1",
"@airgap/moonbeam": "0.13.17-beta.1",
"@airgap/optimism": "0.13.17-beta.1",
"@airgap/polkadot": "0.13.17-beta.1",
"@airgap/sapling-wasm": "0.0.7",
"@airgap/serializer": "0.13.17-beta.0",
"@airgap/substrate": "0.13.17-beta.0",
"@airgap/tezos": "0.13.17-beta.0",
"@airgap/serializer": "0.13.17-beta.1",
"@airgap/substrate": "0.13.17-beta.1",
"@airgap/tezos": "0.13.17-beta.1",
"@angular/animations": "^16.1.1",
"@angular/common": "^16.1.1",
"@angular/core": "^16.1.1",
Expand Down Expand Up @@ -130,6 +130,9 @@
"@types/ledgerhq__hw-transport-u2f": "^4.21.1",
"@types/ledgerhq__hw-transport-webusb": "^4.70.0",
"@walletconnect/client": "^1.4.1",
"@walletconnect/core": "^2.8.0",
"@walletconnect/utils": "^2.8.0",
"@walletconnect/web3wallet": "^1.8.0",
"@zondax/ledger-polkadot": "^0.13.6",
"@zondax/ledger-substrate": "^0.17.3",
"@zxing/browser": "0.1.4",
Expand Down
57 changes: 40 additions & 17 deletions src/app/pages/dapp-confirm/dapp-confirm.page.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,63 @@
import { Component } from '@angular/core'
import { assertNever } from '@airgap/angular-core'
import { Component, OnInit } from '@angular/core'
import { ModalController } from '@ionic/angular'
import WalletConnect from '@walletconnect/client'
import BigNumber from 'bignumber.js'
import { ErrorCategory, handleErrorSentry } from 'src/app/services/sentry-error-handler/sentry-error-handler'
import { WalletconnectV1Handler, WalletconnectV1HandlerContext } from './handler/walletconnect-v1.handler'
import { WalletconnectV2Handler, WalletconnectV2HandlerContext } from './handler/walletconnect-v2.handler'
import { WalletconnectHandler } from './handler/walletconnect.handler'

export interface WalletconnectV1Context extends WalletconnectV1HandlerContext {
version: 1
}

export interface WalletconnectV2Context extends WalletconnectV2HandlerContext {
version: 2
}

export type WalletconnectContext = WalletconnectV1Context | WalletconnectV2Context

@Component({
selector: 'app-dapp-confirm',
templateUrl: './dapp-confirm.page.html',
styleUrls: ['./dapp-confirm.page.scss']
})
export class DappConfirmPage {
private readonly connector: WalletConnect | undefined
public id: string | undefined
public result: string | undefined
export class DappConfirmPage implements OnInit {
public context: WalletconnectContext
public result: string

private readonly handlers = {
v1: new WalletconnectV1Handler(),
v2: new WalletconnectV2Handler()
}

public constructor(private readonly modalController: ModalController) {}

public async ngOnInit(): Promise<void> {
this.result = await this.getHandler().readResult(this.context)
}

public async approveRequest() {
this.connector.approveRequest({
id: new BigNumber(this.id).toNumber(),
result: this.result
})
await this.getHandler().approveRequest(this.context)
this.dismissModal()
}

public async rejectRequest() {
this.connector.rejectRequest({
id: new BigNumber(this.id).toNumber(),
error: {
message: 'USER_REJECTION' // optional
}
})
await this.getHandler().rejectRequest(this.context)
this.dismissModal()
}

public async dismissModal(): Promise<void> {
this.modalController.dismiss().catch(handleErrorSentry(ErrorCategory.NAVIGATION))
}

private getHandler(): WalletconnectHandler<any> {
switch (this.context.version) {
case 1:
return this.handlers['v1']
case 2:
return this.handlers['v2']
default:
assertNever('context', this.context)
}
}
}
31 changes: 31 additions & 0 deletions src/app/pages/dapp-confirm/handler/walletconnect-v1.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import WalletConnect from '@walletconnect/client'

import { WalletconnectHandler } from './walletconnect.handler'

export interface WalletconnectV1HandlerContext {
id: number
result: string
connector: WalletConnect
}

export class WalletconnectV1Handler implements WalletconnectHandler<WalletconnectV1HandlerContext> {
public async readResult(context: WalletconnectV1HandlerContext): Promise<string> {
return context.result
}

public async approveRequest(context: WalletconnectV1HandlerContext): Promise<void> {
context.connector.approveRequest({
id: context.id,
result: context.result
})
}

public async rejectRequest(context: WalletconnectV1HandlerContext): Promise<void> {
context.connector.rejectRequest({
id: context.id,
error: {
message: 'USER_REJECTION'
}
})
}
}
39 changes: 39 additions & 0 deletions src/app/pages/dapp-confirm/handler/walletconnect-v2.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import V2Client from '@walletconnect/web3wallet'
import { getSdkError } from '@walletconnect/utils'

import { WalletconnectHandler } from './walletconnect.handler'

export interface WalletconnectV2HandlerContext {
id: number
topic: string
result: string
client: V2Client
}

export class WalletconnectV2Handler implements WalletconnectHandler<WalletconnectV2HandlerContext> {
public async readResult(context: WalletconnectV2HandlerContext): Promise<string> {
return context.result
}

public async approveRequest(context: WalletconnectV2HandlerContext): Promise<void> {
context.client.respondSessionRequest({
topic: context.topic,
response: {
id: context.id,
jsonrpc: '2.0',
result: context.result
}
})
}

public async rejectRequest(context: WalletconnectV2HandlerContext): Promise<void> {
context.client.respondSessionRequest({
topic: context.topic,
response: {
id: context.id,
jsonrpc: '2.0',
error: getSdkError('USER_REJECTED')
}
})
}
}
5 changes: 5 additions & 0 deletions src/app/pages/dapp-confirm/handler/walletconnect.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface WalletconnectHandler<C> {
readResult(context: C): Promise<string>
approveRequest(context: C): Promise<void>
rejectRequest(context: C): Promise<void>
}
150 changes: 150 additions & 0 deletions src/app/pages/walletconnect/handler/walletconnect-v1.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import WalletConnect from '@walletconnect/client'
import { saveWalletConnectV1Session } from 'src/app/services/walletconnect/helpers'
import {
JSONRPC as _JSONRPC,
EthMethods,
WalletconnectMessage,
Namespace,
SessionRequest,
EthTx,
SwitchEthereumChain
} from '../walletconnect.types'

import { WalletconnectHandler } from './walletconnect.handler'

enum Methods {
SESSION_REQUEST = 'session_request'
}

type JSONRPC<T = unknown> = _JSONRPC<T, EthMethods | Methods>

export interface WalletconnectV1HandlerContext {
request: JSONRPC
connector: WalletConnect
}

function rejectRequest(connector: WalletConnect, request: JSONRPC) {
connector.rejectRequest({
id: request.id,
error: {
message: 'USER_REJECTION' // optional
}
})
}

export class WalletconnectV1Handler implements WalletconnectHandler<WalletconnectV1HandlerContext> {
public async readMessage(context: WalletconnectV1HandlerContext): Promise<WalletconnectMessage> {
switch (context.request.method) {
case Methods.SESSION_REQUEST:
return this.readSessionRequest(context.request as JSONRPC<SessionRequest>, context.connector)
case EthMethods.ETH_SENDTRANSACTION:
return this.readEthSendTransaction(context.request as JSONRPC<EthTx>, context.connector)
case EthMethods.PERSONAL_SIGN_REQUEST:
return this.readPersonalSign(context.request as JSONRPC<string>, context.connector)
case EthMethods.WALLET_SWITCH_ETHEREUM_CHAIN:
return this.readWalletSwitchEthereumChain(context.request as JSONRPC<SwitchEthereumChain>, context.connector)
case EthMethods.ETH_SIGN_TYPED_DATA:
return { type: 'unsupported', namespace: Namespace.ETH, method: EthMethods.ETH_SIGN_TYPED_DATA }
default:
return { type: 'unsupported', namespace: Namespace.ETH }
}
}

private readSessionRequest(request: JSONRPC<SessionRequest>, connector: WalletConnect): WalletconnectMessage {
const chains: string[] | undefined =
request.params[0].chainId !== undefined ? [`${Namespace.ETH}:${request.params[0].chainId}`] : undefined

return {
type: 'permissionRequest',
chains,
dAppMetadata: {
name: request.params[0].peerMeta.name,
description: request.params[0].peerMeta.description,
url: request.params[0].peerMeta.url,
icon: request.params[0].peerMeta.icons ? request.params[0].peerMeta.icons[0] : undefined
},
canOverrideChain: true,
approve: async (accounts: string[]): Promise<void> => {
const account = accounts[0]
if (account === undefined || !(account.startsWith(Namespace.ETH) || account.startsWith('::'))) {
return
}

const [_namespace, chainId, address] = account.split(':')
const ethChainId = parseInt(chainId, 10)

connector.approveSession({
chainId: !isNaN(ethChainId) ? ethChainId : 1,
accounts: [address]
})
await saveWalletConnectV1Session(connector.peerId, connector.session)
},
reject: async (): Promise<void> => {
rejectRequest(connector, request)
}
}
}

private readEthSendTransaction(request: JSONRPC<EthTx>, connector: WalletConnect): WalletconnectMessage {
return {
type: 'signRequest',
version: 1,
namespace: Namespace.ETH,
chain: `${Namespace.ETH}:${connector.chainId}`,
request: {
id: request.id.toString(),
method: EthMethods.ETH_SENDTRANSACTION,
params: request.params
},
cancel: async (): Promise<void> => {
rejectRequest(connector, request)
}
}
}

private readPersonalSign(request: JSONRPC<string>, connector: WalletConnect): WalletconnectMessage {
return {
type: 'signRequest',
version: 1,
namespace: Namespace.ETH,
chain: `${Namespace.ETH}:${connector.chainId}`,
request: {
id: request.id.toString(),
method: EthMethods.PERSONAL_SIGN_REQUEST,
params: request.params
},
cancel: async (): Promise<void> => {
rejectRequest(connector, request)
}
}
}

private readWalletSwitchEthereumChain(request: JSONRPC<SwitchEthereumChain>, connector: WalletConnect): WalletconnectMessage {
return {
type: 'switchAccountRequest',
namespace: Namespace.ETH,
account: connector.accounts[0],
dAppMetadata: {
name: connector.peerMeta.name,
description: connector.peerMeta.description,
url: connector.peerMeta.url,
icon: connector.peerMeta.icons ? connector.peerMeta.icons[0] : undefined
},
request: {
id: request.id.toString(),
method: EthMethods.WALLET_SWITCH_ETHEREUM_CHAIN,
params: request.params
},
respond: async (chainId: number): Promise<void> => {
connector.updateSession({
chainId,
accounts: connector.accounts
})
await saveWalletConnectV1Session(connector.peerId, connector.session)
},
cancel: async (): Promise<void> => {
rejectRequest(connector, request)
}
}
}
}
Loading

0 comments on commit eaa50a2

Please sign in to comment.