Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a636023
feat: wire up GridPlus SafeCard wallet UID validation
gomesalexandre Nov 26, 2025
0cd71ce
fix: properly destructure wallet from pairConnectedDevice result
gomesalexandre Nov 26, 2025
9c9b3e7
fix: capture wallet UID for all GridPlus SafeCard flows
gomesalexandre Nov 26, 2025
334cd26
feat: add UI enhancements for GridPlus SafeCards
gomesalexandre Nov 26, 2025
ba339c9
feat(gridplus): add SafeCard validation for improved UX
gomesalexandre Nov 26, 2025
0f6dd54
fix: update web to use new hdwallet adapter API
gomesalexandre Nov 26, 2025
91b4a59
refactor: rename walletUid to activeWalletId for consistency
gomesalexandre Nov 26, 2025
2225d12
chore: update hdwallet packages to .13
gomesalexandre Nov 26, 2025
5c107c0
fix: remove duplicate connectDevice() call in pairConnectedDevice
gomesalexandre Nov 26, 2025
c454ac6
chore: update hdwallet packages to .14
gomesalexandre Nov 26, 2025
fcc7785
fix: restore physicalDeviceId storage for Add New and Connect buttons
gomesalexandre Nov 26, 2025
a3302da
feat: display specific error message for SafeCard mismatch
gomesalexandre Nov 26, 2025
969a2e2
refactor: update SafeCard tag display to use consistent color
gomesalexandre Nov 26, 2025
864fcce
feat: add context-aware error messages for SafeCard validation
gomesalexandre Nov 26, 2025
bdfea04
feat: wire up expectedType parameter through reconnection flow
gomesalexandre Nov 26, 2025
9238b52
chore: cleanup - remove comments and console.errors
gomesalexandre Nov 26, 2025
a385cfb
[skip ci] refactor: remove password argument from connectDevice call
gomesalexandre Nov 26, 2025
c3400c1
[skip ci] chore: move hardcoded text to translations and cleanup
gomesalexandre Nov 26, 2025
40aea87
[skip ci] fix: store physicalDeviceId before wallet check
gomesalexandre Nov 27, 2025
2c7216a
[skip ci] chore: simplify undefined checks to boolean
gomesalexandre Nov 27, 2025
f027dfc
[skip ci] chore: remove unnecessary method existence check
gomesalexandre Nov 27, 2025
2eea5a4
Merge remote-tracking branch 'origin/develop' into feat_gridplus_vali…
gomesalexandre Nov 27, 2025
11675f3
[skip ci] chore: merge develop
gomesalexandre Nov 27, 2025
f3af080
[skip ci] chore: remove unnecessary braces from single-line return
gomesalexandre Nov 27, 2025
566e685
Merge branch 'develop' into feat_gridplus_validation
gomesalexandre Nov 28, 2025
7c92ff4
feat: bump
gomesalexandre Nov 28, 2025
07bd2c8
chore: trigger CI
gomesalexandre Nov 28, 2025
ba5dca5
fix: lint
gomesalexandre Nov 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,24 @@
"@shapeshiftoss/chain-adapters": "workspace:^",
"@shapeshiftoss/contracts": "workspace:^",
"@shapeshiftoss/errors": "workspace:^",
"@shapeshiftoss/hdwallet-coinbase": "1.62.15",
"@shapeshiftoss/hdwallet-core": "1.62.15",
"@shapeshiftoss/hdwallet-gridplus": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.62.15",
"@shapeshiftoss/hdwallet-keplr": "1.62.15",
"@shapeshiftoss/hdwallet-ledger": "1.62.15",
"@shapeshiftoss/hdwallet-ledger-webhid": "1.62.15",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.62.15",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.62.15",
"@shapeshiftoss/hdwallet-native": "1.62.15",
"@shapeshiftoss/hdwallet-native-vault": "1.62.15",
"@shapeshiftoss/hdwallet-phantom": "1.62.15",
"@shapeshiftoss/hdwallet-trezor": "1.62.15",
"@shapeshiftoss/hdwallet-trezor-connect": "1.62.15",
"@shapeshiftoss/hdwallet-vultisig": "1.62.15",
"@shapeshiftoss/hdwallet-walletconnect": "1.62.15",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.62.15",
"@shapeshiftoss/hdwallet-coinbase": "1.62.16",
"@shapeshiftoss/hdwallet-core": "1.62.16",
"@shapeshiftoss/hdwallet-gridplus": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.62.16",
"@shapeshiftoss/hdwallet-keplr": "1.62.16",
"@shapeshiftoss/hdwallet-ledger": "1.62.16",
"@shapeshiftoss/hdwallet-ledger-webhid": "1.62.16",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.62.16",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.62.16",
"@shapeshiftoss/hdwallet-native": "1.62.16",
"@shapeshiftoss/hdwallet-native-vault": "1.62.16",
"@shapeshiftoss/hdwallet-phantom": "1.62.16",
"@shapeshiftoss/hdwallet-trezor": "1.62.16",
"@shapeshiftoss/hdwallet-trezor-connect": "1.62.16",
"@shapeshiftoss/hdwallet-vultisig": "1.62.16",
"@shapeshiftoss/hdwallet-walletconnect": "1.62.16",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.62.16",
"@shapeshiftoss/swapper": "workspace:^",
"@shapeshiftoss/types": "workspace:^",
"@shapeshiftoss/unchained-client": "workspace:^",
Expand Down
18 changes: 18 additions & 0 deletions packages/chain-adapters/src/error/ErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ export const ErrorHandler = async (err: unknown, metadata?: ErrorMetadata): Prom
})
}

if (
err instanceof Error &&
err.message.includes('Remove inserted SafeCard to access internal GridPlus wallet')
) {
throw new ChainAdapterError(err, {
translation: 'chainAdapters.errors.gridplus.removeSafeCard',
})
}

if (
err instanceof Error &&
err.message.includes("Active SafeCard doesn't match expected SafeCard")
) {
throw new ChainAdapterError(err, {
translation: 'chainAdapters.errors.gridplus.wrongSafeCard',
})
}

if ((err as AxiosError).isAxiosError) {
const response = JSON.stringify((err as AxiosError).response?.data)
if (metadata) throw new ChainAdapterError(response, metadata)
Expand Down
13 changes: 11 additions & 2 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1429,7 +1429,9 @@
"renamed": "SafeCard renamed",
"deleted": "SafeCard deleted",
"deletedDescription": "Portfolio data has been cleared",
"warning": "Each SafeCard maintains its own portfolio. Make sure to insert the correct SafeCard in your device before connecting."
"warning": "Each SafeCard maintains its own portfolio. Make sure to insert the correct SafeCard in your device before connecting.",
"safeCardLabel": "SafeCard",
"internalSafeCardLabel": "Internal SafeCard"
},
"connect": {
"header": "Connect New SafeCard",
Expand Down Expand Up @@ -1463,6 +1465,9 @@
"deviceNotConnected": "Device not connected. Call connectDevice first.",
"connectFailure": "Unable to connect GridPlus wallet",
"unknown": "An unexpected error occurred communicating with GridPlus wallet"
},
"menu": {
"switchSafeCard": "Switch SafeCard"
}
},
"ledger": {
Expand Down Expand Up @@ -2518,7 +2523,11 @@
"getFeeData": "Failed to estimate network fees.",
"getPublicKey": "Failed to get public key from wallet.",
"getUtxos": "Failed to get utxos.",
"getValidator": "Failed to get validator."
"getValidator": "Failed to get validator.",
"gridplus": {
"wrongSafeCard": "Wrong SafeCard inserted. Please insert the correct SafeCard and try again.",
"removeSafeCard": "Remove inserted SafeCard to access internal GridPlus wallet."
}
}
},
"actionCenter": {
Expand Down
38 changes: 38 additions & 0 deletions src/context/WalletProvider/GridPlus/components/GridPlusMenu.tsx
Comment thread
gomesalexandre marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { RepeatIcon } from '@chakra-ui/icons'
import { MenuItem } from '@chakra-ui/react'
import { useCallback } from 'react'
import { useTranslate } from 'react-polyglot'

import { ManageAccountsMenuItem } from '@/components/Layout/Header/NavBar/ManageAccountsMenuItem'
import { WalletActions } from '@/context/WalletProvider/actions'
import { useWallet } from '@/hooks/useWallet/useWallet'

const switchIcon = <RepeatIcon />

type GridPlusMenuProps = {
onClose?: () => void
}

export const GridPlusMenu = ({ onClose }: GridPlusMenuProps) => {
const translate = useTranslate()
const { dispatch } = useWallet()

const handleSwitchSafeCard = useCallback(() => {
dispatch({ type: WalletActions.RESET_STATE })
dispatch({
type: WalletActions.SET_INITIAL_ROUTE,
payload: '/gridplus/connect',
})
dispatch({ type: WalletActions.SET_WALLET_MODAL, payload: true })
onClose?.()
}, [dispatch, onClose])

return (
<>
<MenuItem icon={switchIcon} onClick={handleSwitchSafeCard}>
{translate('walletProvider.gridplus.menu.switchSafeCard')}
</MenuItem>
<ManageAccountsMenuItem onClose={onClose} />
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,21 @@ export const GridPlusPair = () => {
throw new Error(translate('walletProvider.gridplus.errors.adapterNotAvailable'))
}

const wallet = await pairConnectedDevice({
const { wallet, activeWalletId, type } = await pairConnectedDevice({
adapter,
deviceId,
pairingCode,
dispatch: appDispatch,
})

appDispatch(gridplusSlice.actions.setActiveSafeCard(safeCardUuid))

navigate('/gridplus/setup', {
state: { safeCardUuid, wallet, safeCardWalletId: `gridplus:${safeCardUuid}` },
state: {
safeCardUuid,
wallet,
safeCardWalletId: `gridplus:${safeCardUuid}`,
activeWalletId,
type,
},
})
} catch (err) {
setError((err as Error).message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type LocationState = {
defaultName?: string
needsPairing?: boolean
deviceId?: string
activeWalletId?: string
type?: 'external' | 'internal'
}

export const GridPlusSetup = () => {
Expand Down Expand Up @@ -64,6 +66,9 @@ export const GridPlusSetup = () => {

try {
let finalWallet = wallet
// Use activeWalletId and type from state if available (from pairing flow)
let activeWalletId: string | undefined = state?.activeWalletId
let type: 'external' | 'internal' | undefined = state?.type

if (!finalWallet) {
const adapter = await getAdapter(KeyManager.GridPlus)
Expand All @@ -76,12 +81,13 @@ export const GridPlusSetup = () => {
if (!deviceId) {
throw new Error(translate('walletProvider.gridplus.errors.deviceIdRequired'))
}
finalWallet = await pairConnectedDevice({
const result = await pairConnectedDevice({
adapter,
deviceId,
pairingCode,
dispatch: appDispatch,
})
finalWallet = result.wallet
activeWalletId = result.activeWalletId
type = result.type
} else {
const connectionDeviceId = physicalDeviceId || deviceId || state?.deviceId
if (!connectionDeviceId) {
Expand All @@ -105,13 +111,23 @@ export const GridPlusSetup = () => {
throw new Error(translate('walletProvider.gridplus.errors.walletNotAvailable'))
}

// If we don't have activeWalletId yet (e.g., when adding new SafeCard with existing wallet),
// we need to fetch it from the wallet
if (activeWalletId === undefined || type === undefined) {
const validation = await finalWallet.validateActiveWallet()
activeWalletId = validation.activeWalletId
type = validation.type
}

const safeCardWalletId = state?.safeCardWalletId || `gridplus:${safeCardUuid}`
const finalSafeCardName = safeCardName.trim() || defaultName

appDispatch(
gridplusSlice.actions.addSafeCard({
id: safeCardUuid,
name: finalSafeCardName,
activeWalletId,
type,
}),
)

Expand Down Expand Up @@ -161,6 +177,8 @@ export const GridPlusSetup = () => {
defaultName,
state?.safeCardWalletId,
state?.deviceId,
state?.activeWalletId,
state?.type,
physicalDeviceId,
translate,
getAdapter,
Expand Down
23 changes: 21 additions & 2 deletions src/context/WalletProvider/GridPlus/components/SafeCardRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Box, Button, ButtonGroup, HStack, IconButton, Input, Text, VStack } from '@chakra-ui/react'
import {
Box,
Button,
ButtonGroup,
HStack,
IconButton,
Input,
Tag,
Text,
VStack,
} from '@chakra-ui/react'
import { memo } from 'react'
import { FaWallet } from 'react-icons/fa'
import { IoMdCreate, IoMdTrash } from 'react-icons/io'
Expand Down Expand Up @@ -68,7 +78,16 @@ export const SafeCardRow = memo(
/>
) : (
<VStack align='start' flex={1} spacing={0}>
<Text fontWeight='medium'>{safeCard.name}</Text>
<HStack spacing={2}>
<Text fontWeight='medium'>{safeCard.name}</Text>
{safeCard.type && (
Comment thread
gomesalexandre marked this conversation as resolved.
<Tag size='sm' colorScheme='blue' variant='subtle'>
{safeCard.type === 'internal'
? translate('walletProvider.gridplus.list.internalSafeCardLabel')
: translate('walletProvider.gridplus.list.safeCardLabel')}
</Tag>
)}
</HStack>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{safeCard.lastConnectedAt && (
<Text fontSize='xs' color='text.subtle'>
{translate('walletProvider.gridplus.list.lastConnected', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export const useGridPlusConnection = () => {
setError(null)

try {
appDispatch(gridplusSlice.actions.setActiveSafeCard(id))
const safeCard = safeCards.find(card => card.id === id)
const expectedWalletUid = safeCard?.activeWalletId

const safeCardWalletId = `gridplus:${id}`
const connectionDeviceId = getConnectionDeviceId()
Expand All @@ -141,6 +142,8 @@ export const useGridPlusConnection = () => {
const wallet = await connectAndPairDevice({
adapter,
deviceId: connectionDeviceId,
expectedActiveWalletId: expectedWalletUid,
expectedType: safeCard?.type,
dispatch: appDispatch,
})

Expand All @@ -152,13 +155,17 @@ export const useGridPlusConnection = () => {
return
}

appDispatch(gridplusSlice.actions.setActiveSafeCard(id))

finalizeWalletSetup({
wallet,
safeCardWalletId,
walletDispatch,
localWallet,
navigate,
appDispatch,
activeWalletId: safeCard?.activeWalletId,
type: safeCard?.type,
})
} catch (e) {
setConnectingCardId(null)
Expand All @@ -167,6 +174,7 @@ export const useGridPlusConnection = () => {
},
[
appDispatch,
safeCards,
getConnectionDeviceId,
getAdapterWithKeyring,
walletDispatch,
Expand Down
Loading