Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/ Switch account action #1046

Merged
merged 12 commits into from
Nov 27, 2024
Merged
8 changes: 8 additions & 0 deletions src/consts/dappCommunication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const ORIGINS_WHITELISTED_TO_ALL_ACCOUNTS = [
'https://legends.ambire.com',
'https://legends-staging.ambire.com',
'http://localhost:19006',
'http://localhost:19007'
]

export { ORIGINS_WHITELISTED_TO_ALL_ACCOUNTS }
18 changes: 16 additions & 2 deletions src/controllers/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
Action,
BenzinAction,
DappRequestAction,
SignMessageAction
SignMessageAction,
SwitchAccountAction
} from '../../interfaces/actions'
import { NotificationManager } from '../../interfaces/notification'
import { WindowManager } from '../../interfaces/window'
Expand All @@ -20,7 +21,14 @@ import EventEmitter from '../eventEmitter/eventEmitter'
import { SelectedAccountController } from '../selectedAccount/selectedAccount'

// TODO: Temporarily. Refactor imports across the codebase to ref /interfaces/actions instead.
export type { Action, AccountOpAction, SignMessageAction, BenzinAction, DappRequestAction }
export type {
SwitchAccountAction,
Action,
AccountOpAction,
SignMessageAction,
BenzinAction,
DappRequestAction
}

/**
* The ActionsController is responsible for storing the converted userRequests
Expand Down Expand Up @@ -74,6 +82,9 @@ export class ActionsController extends EventEmitter {
if (a.type === 'benzin') {
return a.userRequest.meta.accountAddr === this.#selectedAccount.account?.addr
}
if (a.type === 'switchAccount') {
return a.userRequest.meta.switchToAccountAddr !== this.#selectedAccount.account?.addr
}

return true
})
Expand Down Expand Up @@ -274,6 +285,9 @@ export class ActionsController extends EventEmitter {
if (a.type === 'benzin') {
return a.userRequest.meta.accountAddr !== address
}
if (a.type === 'switchAccount') {
return a.userRequest.meta.switchToAccountAddr !== address
}

return true
})
Expand Down
83 changes: 58 additions & 25 deletions src/controllers/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getAddress, getBigInt, Interface, isAddress } from 'ethers'
import AmbireAccount from '../../../contracts/compiled/AmbireAccount.json'
import AmbireFactory from '../../../contracts/compiled/AmbireFactory.json'
import EmittableError from '../../classes/EmittableError'
import { ORIGINS_WHITELISTED_TO_ALL_ACCOUNTS } from '../../consts/dappCommunication'
import { AMBIRE_ACCOUNT_FACTORY, SINGLETON } from '../../consts/deploy'
import {
BIP44_LEDGER_DERIVATION_TEMPLATE,
Expand Down Expand Up @@ -55,6 +56,7 @@ import { GasRecommendation, getGasPriceRecommendations } from '../../libs/gasPri
import { humanizeAccountOp } from '../../libs/humanizer'
import { KeyIterator } from '../../libs/keyIterator/keyIterator'
import {
buildSwitchAccountUserRequest,
getAccountOpsForSimulation,
makeBasicAccountOpAction,
makeSmartAccountOpAction
Expand Down Expand Up @@ -952,6 +954,21 @@ export class MainController extends EventEmitter {
)
}

#getUserRequestAccountError(dappOrigin: string, fromAccountAddr: string): string | null {
if (ORIGINS_WHITELISTED_TO_ALL_ACCOUNTS.includes(dappOrigin)) {
const isAddressInAccounts = this.accounts.accounts.some((a) => a.addr === fromAccountAddr)

if (isAddressInAccounts) return null

return 'The dApp is trying to sign using an address that is not imported in the extension.'
}
const isAddressSelected = this.selectedAccount.account?.addr === fromAccountAddr

if (isAddressSelected) return null

return 'The dApp is trying to sign using an address that is not selected in the extension.'
}

async buildUserRequestFromDAppRequest(
request: DappProviderRequest,
dappPromise: {
Expand Down Expand Up @@ -1012,17 +1029,6 @@ export class MainController extends EventEmitter {
throw ethErrors.rpc.invalidRequest('No msg request to sign')
}
const msgAddress = getAddress(msg?.[1])
// TODO: if address is in this.accounts in theory the user should be able to sign
// e.g. if an acc from the wallet is used as a signer of another wallet
if (msgAddress !== this.selectedAccount.account.addr) {
dappPromise.reject(
ethErrors.provider.userRejectedRequest(
// if updating, check https://github.com/AmbireTech/ambire-wallet/pull/1627
'the dApp is trying to sign using an address different from the currently selected account. Try re-connecting.'
)
)
return
}

const network = this.networks.networks.find(
(n) => Number(n.chainId) === Number(dapp?.chainId)
Expand Down Expand Up @@ -1054,17 +1060,6 @@ export class MainController extends EventEmitter {
throw ethErrors.rpc.invalidRequest('No msg request to sign')
}
const msgAddress = getAddress(msg?.[0])
// TODO: if address is in this.accounts in theory the user should be able to sign
// e.g. if an acc from the wallet is used as a signer of another wallet
if (msgAddress !== this.selectedAccount.account.addr) {
dappPromise.reject(
ethErrors.provider.userRejectedRequest(
// if updating, check https://github.com/AmbireTech/ambire-wallet/pull/1627
'the dApp is trying to sign using an address different from the currently selected account. Try re-connecting.'
)
)
return
}

const network = this.networks.networks.find(
(n) => Number(n.chainId) === Number(dapp?.chainId)
Expand Down Expand Up @@ -1130,10 +1125,46 @@ export class MainController extends EventEmitter {
}
}

if (userRequest) {
if (!userRequest) return

const isASignOperationRequestedForAnotherAccount =
userRequest.meta.isSignAction &&
userRequest.meta.accountAddr !== this.selectedAccount.account?.addr

// We can simply add the user request if it's not a sign operation
// for another account
if (!isASignOperationRequestedForAnotherAccount) {
await this.addUserRequest(userRequest, withPriority)
this.emitUpdate()
return
}

const accountError = this.#getUserRequestAccountError(
dappPromise.session.origin,
userRequest.meta.accountAddr
)

if (accountError) {
dappPromise.reject(ethErrors.provider.userRejectedRequest(accountError))
return
}

const network = this.networks.networks.find((n) => Number(n.chainId) === Number(dapp?.chainId))

if (!network) {
throw ethErrors.provider.chainDisconnected('Transaction failed - unknown network')
}

await this.addUserRequest(userRequest, false)
await this.addUserRequest(
buildSwitchAccountUserRequest({
nextUserRequest: userRequest,
networkId: network.id,
selectedAccountAddr: userRequest.meta.accountAddr,
session: dappPromise.session,
rejectUserRequest: this.rejectUserRequest.bind(this)
}),
true
)
}

async buildTransferUserRequest(
Expand Down Expand Up @@ -1421,7 +1452,7 @@ export class MainController extends EventEmitter {
this.actions.addOrUpdateAction(accountOpAction, withPriority, executionType)
}
} else {
let actionType: 'dappRequest' | 'benzin' | 'signMessage' = 'dappRequest'
let actionType: 'dappRequest' | 'benzin' | 'signMessage' | 'switchAccount' = 'dappRequest'

if (req.action.kind === 'typedMessage' || req.action.kind === 'message') {
actionType = 'signMessage'
Expand All @@ -1441,6 +1472,8 @@ export class MainController extends EventEmitter {
}
}
if (req.action.kind === 'benzin') actionType = 'benzin'
if (req.action.kind === 'switchAccount') actionType = 'switchAccount'

this.actions.addOrUpdateAction(
{
id,
Expand Down
19 changes: 18 additions & 1 deletion src/interfaces/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { AccountOp } from '../libs/accountOp/accountOp'
import { Account } from './account'
import { DappUserRequest, SignUserRequest, UserRequest } from './userRequest'

export type SwitchAccountAction = {
id: UserRequest['id']
type: 'switchAccount'
userRequest: {
meta: {
accountAddr: Account['addr']
switchToAccountAddr: Account['addr']
}
}
}

export type AccountOpAction = {
id: SignUserRequest['id']
type: 'accountOp'
Expand All @@ -25,4 +37,9 @@ export type DappRequestAction = {
userRequest: DappUserRequest
}

export type Action = AccountOpAction | SignMessageAction | BenzinAction | DappRequestAction
export type Action =
| SwitchAccountAction
| AccountOpAction
| SignMessageAction
| BenzinAction
| DappRequestAction
2 changes: 1 addition & 1 deletion src/interfaces/userRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export interface SignUserRequest {
export interface DappUserRequest {
id: string | number
action: {
kind: Exclude<string, 'calls' | 'message' | 'typedMessage' | 'benzin'>
kind: Exclude<string, 'calls' | 'message' | 'typedMessage' | 'benzin' | 'switchAccount'>
params: any
}
session: DappProviderRequest['session']
Expand Down
41 changes: 41 additions & 0 deletions src/libs/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AccountOpAction, Action } from '../../controllers/actions/actions'
import { Account, AccountId } from '../../interfaces/account'
import { DappProviderRequest } from '../../interfaces/dapp'
import { Network, NetworkId } from '../../interfaces/network'
import { Calls, SignUserRequest, UserRequest } from '../../interfaces/userRequest'
import generateSpoofSig from '../../utils/generateSpoofSig'
Expand Down Expand Up @@ -30,6 +31,46 @@ export const batchCallsFromUserRequests = ({
)
}

export const buildSwitchAccountUserRequest = ({
nextUserRequest,
selectedAccountAddr,
networkId,
session,
rejectUserRequest
}: {
nextUserRequest: UserRequest
selectedAccountAddr: string
networkId: Network['id']
session: DappProviderRequest['session']
rejectUserRequest: (reason: string, userRequestId: string | number) => void
}): UserRequest => {
const userRequestId = nextUserRequest.id

return {
id: new Date().getTime(),
action: {
kind: 'switchAccount',
params: {
accountAddr: selectedAccountAddr,
switchToAccountAddr: nextUserRequest.meta.accountAddr,
nextRequestType: nextUserRequest.action.kind,
networkId
}
},
session,
meta: {
isSignAction: false
},
dappPromise: {
session,
resolve: () => {},
reject: () => {
rejectUserRequest('Switch account request rejected', userRequestId)
}
}
}
}

export const makeSmartAccountOpAction = ({
account,
networkId,
Expand Down
Loading