-
-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(suite): walletconnect implementation for evm
- Loading branch information
Showing
31 changed files
with
2,445 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "@trezor/suite-walletconnect", | ||
"version": "1.0.0", | ||
"private": true, | ||
"license": "See LICENSE.md in repo root", | ||
"sideEffects": false, | ||
"main": "src/index", | ||
"scripts": { | ||
"depcheck": "yarn g:depcheck", | ||
"type-check": "yarn g:tsc --build" | ||
}, | ||
"dependencies": { | ||
"@reduxjs/toolkit": "1.9.5", | ||
"@reown/walletkit": "^1.1.1", | ||
"@suite-common/redux-utils": "workspace:*", | ||
"@suite-common/wallet-config": "workspace:*", | ||
"@suite-common/wallet-core": "workspace:*", | ||
"@suite-common/wallet-types": "workspace:*", | ||
"@trezor/connect": "workspace:*", | ||
"@trezor/suite-desktop-api": "workspace:*", | ||
"@walletconnect/core": "^2.17.2", | ||
"@walletconnect/utils": "^2.17.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import { WalletKitTypes } from '@reown/walletkit'; | ||
|
||
import { createThunk } from '@suite-common/redux-utils'; | ||
import { getNetwork } from '@suite-common/wallet-config'; | ||
import { selectAccounts, selectSelectedDevice } from '@suite-common/wallet-core'; | ||
import * as trezorConnectPopupActions from '@trezor/suite-desktop-connect-popup'; | ||
import TrezorConnect from '@trezor/connect'; | ||
import { getAccountIdentity } from '@suite-common/wallet-utils'; | ||
|
||
import { WALLETCONNECT_MODULE } from '../walletConnectConstants'; | ||
import { WalletConnectAdapter } from '../walletConnectTypes'; | ||
|
||
const ethereumRequestThunk = createThunk< | ||
void, | ||
{ | ||
event: WalletKitTypes.SessionRequest; | ||
} | ||
>(`${WALLETCONNECT_MODULE}/ethereumRequest`, async ({ event }, { dispatch, getState }) => { | ||
const device = selectSelectedDevice(getState()); | ||
const getAccount = (address: string, chainId?: number) => { | ||
const account = selectAccounts(getState()).find( | ||
a => | ||
a.descriptor.toLowerCase() === address.toLowerCase() && | ||
a.networkType === 'ethereum' && | ||
(!chainId || getNetwork(a.symbol).chainId === chainId), | ||
); | ||
if (!account) { | ||
throw new Error('Account not found'); | ||
} | ||
|
||
return account; | ||
}; | ||
|
||
switch (event.params.request.method) { | ||
case 'personal_sign': { | ||
const [message, address] = event.params.request.params; | ||
const account = getAccount(address); | ||
const response = await dispatch( | ||
trezorConnectPopupActions.connectPopupCallThunk({ | ||
id: 0, | ||
method: 'ethereumSignMessage', | ||
payload: { | ||
path: account.path, | ||
message, | ||
hex: true, | ||
device, | ||
useEmptyPassphrase: device?.useEmptyPassphrase, | ||
}, | ||
processName: 'WalletConnect', | ||
origin: event.verifyContext.verified.origin, | ||
}), | ||
).unwrap(); | ||
if (!response.success) { | ||
console.error('personal_sign error', response); | ||
throw new Error('personal_sign error'); | ||
} | ||
|
||
return response.payload.signature; | ||
} | ||
case 'eth_signTypedData_v4': { | ||
const [address, data] = event.params.request.params; | ||
const account = getAccount(address); | ||
const response = await dispatch( | ||
trezorConnectPopupActions.connectPopupCallThunk({ | ||
id: 0, | ||
method: 'ethereumSignTypedData', | ||
payload: { | ||
path: account.path, | ||
data: JSON.parse(data), | ||
metamask_v4_compat: true, | ||
device, | ||
useEmptyPassphrase: device?.useEmptyPassphrase, | ||
}, | ||
processName: 'WalletConnect', | ||
origin: event.verifyContext.verified.origin, | ||
}), | ||
).unwrap(); | ||
if (!response.success) { | ||
console.error('eth_signTypedData_v4 error', response); | ||
throw new Error('eth_signTypedData_v4 error'); | ||
} | ||
|
||
return response.payload.signature; | ||
} | ||
case 'eth_sendTransaction': { | ||
const chainId = Number(event.params.chainId.replace('eip155:', '')); | ||
const transaction = event.params.request.params[0]; | ||
const account = getAccount(transaction.from, chainId); | ||
if (account.networkType !== 'ethereum') { | ||
throw new Error('Account is not Ethereum'); | ||
} | ||
if (!transaction.gasPrice) { | ||
// Fee not provided, estimate it | ||
const feeLevels = await TrezorConnect.blockchainEstimateFee({ | ||
coin: account.symbol, | ||
identity: getAccountIdentity(account), | ||
request: { | ||
blocks: [2], | ||
specific: { | ||
from: account.descriptor, | ||
}, | ||
}, | ||
}); | ||
if (!feeLevels.success) { | ||
throw new Error('eth_sendTransaction cannot estimate fee'); | ||
} | ||
transaction.gasPrice = feeLevels.payload.levels[0]?.feePerUnit; | ||
} | ||
const payload = { | ||
path: account.path, | ||
transaction: { | ||
...transaction, | ||
gasLimit: transaction.gas ?? '21000', | ||
nonce: account.misc.nonce, | ||
chainId, | ||
push: true, | ||
}, | ||
device, | ||
useEmptyPassphrase: device?.useEmptyPassphrase, | ||
}; | ||
const signResponse = await dispatch( | ||
trezorConnectPopupActions.connectPopupCallThunk({ | ||
id: 0, | ||
method: 'ethereumSignTransaction', | ||
payload, | ||
processName: 'WalletConnect', | ||
origin: event.verifyContext.verified.origin, | ||
}), | ||
).unwrap(); | ||
if (!signResponse.success) { | ||
console.error('eth_sendTransaction error', signResponse); | ||
throw new Error('eth_sendTransaction error'); | ||
} | ||
|
||
const pushResponse = await TrezorConnect.pushTransaction({ | ||
coin: account.symbol, | ||
identity: getAccountIdentity(account), | ||
tx: signResponse.payload.serializedTx, | ||
}); | ||
if (!pushResponse.success) { | ||
console.error('eth_sendTransaction push error', pushResponse); | ||
throw new Error('eth_sendTransaction push error'); | ||
} | ||
|
||
return pushResponse.payload.txid; | ||
} | ||
case 'wallet_switchEthereumChain': { | ||
const [chainId] = event.params.request.params; | ||
|
||
return chainId; | ||
} | ||
} | ||
}); | ||
|
||
export const ethereumAdapter = { | ||
methods: [ | ||
'eth_sendTransaction', | ||
'eth_signTypedData_v4', | ||
'personal_sign', | ||
'wallet_switchEthereumChain', | ||
], | ||
networkType: 'ethereum', | ||
requestThunk: ethereumRequestThunk, | ||
} satisfies WalletConnectAdapter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Account } from '@suite-common/wallet-types'; | ||
import { getNetwork } from '@suite-common/wallet-config'; | ||
|
||
import { ethereumAdapter } from './ethereum'; | ||
import { WalletConnectAdapter, WalletConnectNamespace } from '../walletConnectTypes'; | ||
|
||
export const adapters: WalletConnectAdapter[] = [ | ||
ethereumAdapter, | ||
// TODO: solanaAdapter | ||
// TODO: bitcoinAdapter | ||
]; | ||
|
||
export const getAdapterByMethod = (method: string) => | ||
adapters.find(adapter => adapter.methods.includes(method)); | ||
|
||
export const getAdapterByNetwork = (networkType: string) => | ||
adapters.find(adapter => adapter.networkType === networkType); | ||
|
||
export const getAllMethods = () => adapters.flatMap(adapter => adapter.methods); | ||
|
||
export const getNamespaces = (accounts: Account[]) => { | ||
const eip155 = { | ||
chains: [], | ||
accounts: [], | ||
methods: getAllMethods(), | ||
events: ['accountsChanged', 'chainChanged'], | ||
} as WalletConnectNamespace; | ||
|
||
accounts.forEach(account => { | ||
const network = getNetwork(account.symbol); | ||
const { chainId, networkType } = network; | ||
|
||
if (!account.visible || !getAdapterByNetwork(networkType)) return; | ||
|
||
const walletConnectChainId = `eip155:${chainId}`; | ||
if (!eip155.chains.includes(walletConnectChainId)) { | ||
eip155.chains.push(walletConnectChainId); | ||
} | ||
eip155.accounts.push(`${walletConnectChainId}:${account.descriptor}`); | ||
}); | ||
|
||
return { eip155 }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './walletConnectActions'; | ||
export * from './walletConnectThunks'; | ||
export * from './walletConnectMiddleware'; | ||
export * from './walletConnectReducer'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { createAction } from '@reduxjs/toolkit'; | ||
|
||
import { PendingConnectionProposal, WalletConnectSession } from './walletConnectTypes'; | ||
|
||
export const ACTION_PREFIX = '@trezor/suite-walletconnect'; | ||
|
||
const saveSession = createAction( | ||
`${ACTION_PREFIX}/saveSession`, | ||
(payload: WalletConnectSession) => ({ | ||
payload, | ||
}), | ||
); | ||
|
||
const removeSession = createAction( | ||
`${ACTION_PREFIX}/removeSession`, | ||
(payload: { topic: string }) => ({ | ||
payload, | ||
}), | ||
); | ||
|
||
const createSessionProposal = createAction( | ||
`${ACTION_PREFIX}/createSessionProposal`, | ||
(payload: PendingConnectionProposal) => ({ | ||
payload, | ||
}), | ||
); | ||
|
||
const clearSessionProposal = createAction(`${ACTION_PREFIX}/clearSessionProposal`); | ||
|
||
const expireSessionProposal = createAction(`${ACTION_PREFIX}/expireSessionProposal`); | ||
|
||
export const walletConnectActions = { | ||
saveSession, | ||
removeSession, | ||
createSessionProposal, | ||
clearSessionProposal, | ||
expireSessionProposal, | ||
} as const; |
10 changes: 10 additions & 0 deletions
10
packages/suite-walletconnect/src/walletConnectConstants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export const WALLETCONNECT_MODULE = '@suite/walletconnect'; | ||
|
||
export const PROJECT_ID = '203549d0480d0f24d994780f34889b03'; | ||
|
||
export const WALLETCONNECT_METADATA = { | ||
name: 'Trezor Suite', | ||
description: 'Manage your Trezor device', | ||
url: 'https://suite.trezor.io', | ||
icons: ['https://trezor.io/favicon/apple-touch-icon.png'], | ||
}; |
Oops, something went wrong.