Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/smart-wallet-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@1001-digital/components.evm': minor
---

Make wallet onboarding composable with external-only and in-app-only connector
filters, direct create or restore entry points for in-app wallets, and back
events that bubble through the connect and SIWE auth wrappers.
59 changes: 42 additions & 17 deletions docs/components/evm.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ Components that require wallet or browser APIs are marked client-only by the Nux

## Wallet Connection

| Component | Purpose | Key API |
| ------------------------- | ----------------------------------- | ---------------------------------------------------- |
| `EvmConnect` | Inline wallet connection UI. | Emits `connecting`, `connected`. |
| `EvmConnectDialog` | Dialog-based wallet connection. | Prop `className`; emits `connected`, `disconnected`. |
| `EvmConnectionStatus` | Displays current connection status. | Uses wagmi connection state. |
| `EvmConnectorQR` | Generic QR connector UI. | Prop `uri`. |
| `EvmMetaMaskQR` | MetaMask QR flow. | Prop `uri`; emits `back`. |
| `EvmWalletConnectQR` | WalletConnect QR flow. | Prop `uri`. |
| `EvmWalletConnectWallets` | WalletConnect wallet list. | Prop `uri`; emits `back`. |
| `EvmInAppWalletSetup` | In-app wallet setup UI. | Prop `note`; emits `connected`, `back`. |
| `EvmSeedPhraseInput` | Seed phrase input. | `v-model`, `disabled`; emits `valid`, `submit`. |
| Component | Purpose | Key API |
| ------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------- |
| `EvmConnect` | Inline wallet connection UI. | Props `connectorFilter`, `inAppWalletInitialStep`; emits `connecting`, `connected`, `back`. |
| `EvmConnectDialog` | Dialog-based wallet connection. | Connect props plus `className`; emits `connected`, `disconnected`, `back`. |
| `EvmConnectionStatus` | Displays current connection status. | Uses wagmi connection state. |
| `EvmConnectorQR` | Generic QR connector UI. | Prop `uri`. |
| `EvmMetaMaskQR` | MetaMask QR flow. | Prop `uri`; emits `back`. |
| `EvmWalletConnectQR` | WalletConnect QR flow. | Prop `uri`. |
| `EvmWalletConnectWallets` | WalletConnect wallet list. | Prop `uri`; emits `back`. |
| `EvmInAppWalletSetup` | In-app wallet setup UI. | Props `note`, `initialStep`; emits `connected`, `back`. |
| `EvmSeedPhraseInput` | Seed phrase input. | `v-model`, `disabled`; emits `valid`, `submit`. |

```vue
<template>
Expand All @@ -34,6 +34,31 @@ Components that require wallet or browser APIs are marked client-only by the Nux
</template>
```

Use `connectorFilter` to let the host application own a wallet-choice screen.
`external` excludes the in-app connector, while `in-app` opens the in-app
setup without showing the connector list. The optional
`inAppWalletInitialStep` can skip the setup's internal chooser:

```vue
<template>
<EvmConnectAuth
v-if="walletPath === 'existing'"
connector-filter="external"
:get-nonce="getNonce"
:verify="verify"
@back="walletPath = null"
/>
<EvmConnectAuth
v-else-if="walletPath === 'create'"
connector-filter="in-app"
in-app-wallet-initial-step="create"
:get-nonce="getNonce"
:verify="verify"
@back="walletPath = null"
/>
</template>
```

## Accounts, Inputs, and Profiles

| Component | Purpose | Key API |
Expand Down Expand Up @@ -117,12 +142,12 @@ The transaction flow checks chain selection, requests a transaction, waits for a

## SIWE and Auth

| Component | Purpose | Key API |
| ---------------------- | ------------------------------------- | ------------------------------------------------------------------------- |
| `EvmSiwe` | Inline Sign-In with Ethereum flow. | Requires `getNonce` and `verify`; emits `authenticated`, `error`. |
| `EvmSiweDialog` | Dialog-based SIWE flow. | SIWE props plus `className`; emits `authenticated`, `signedOut`, `error`. |
| `EvmConnectAuth` | Wallet connect plus SIWE auth flow. | SIWE props; emits connection and auth events. |
| `EvmConnectAuthDialog` | Dialog wrapper for connect plus SIWE. | SIWE props plus `className`, `noTrigger`. |
| Component | Purpose | Key API |
| ---------------------- | ------------------------------------- | --------------------------------------------------------------------------------------- |
| `EvmSiwe` | Inline Sign-In with Ethereum flow. | Requires `getNonce` and `verify`; emits `authenticated`, `error`. |
| `EvmSiweDialog` | Dialog-based SIWE flow. | SIWE props plus `className`; emits `authenticated`, `signedOut`, `error`. |
| `EvmConnectAuth` | Wallet connect plus SIWE auth flow. | SIWE and connect props; emits connection, auth, and `back` events. |
| `EvmConnectAuthDialog` | Dialog wrapper for connect plus SIWE. | SIWE and connect props plus `className`, `noTrigger`; emits auth and connection events. |

```vue
<template>
Expand Down
64 changes: 52 additions & 12 deletions packages/components.evm/src/components/EvmConnect.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
<template>
<EvmInAppWalletSetup
v-if="showInAppSetup"
@connected="onInAppConnected"
@back="showInAppSetup = false"
/>
<template v-if="showInAppSetup">
<EvmInAppWalletSetup
v-if="inAppConnector"
:key="inAppSetupKey"
:initial-step="inAppWalletInitialStep"
@connected="onInAppConnected"
@back="onInAppBack"
/>
<template v-else>
<Alert type="error">In-app wallet is unavailable.</Alert>
<Button
class="link muted small"
@click="onInAppBack"
>
<Icon name="chevron-left" />
<span>Back</span>
</Button>
</template>
</template>
<template v-else-if="errorMessage">
<Alert type="error">
{{ errorMessage }}
Expand Down Expand Up @@ -38,7 +52,7 @@
class="wallet-options"
>
<Button
v-for="connector in shownConnectors"
v-for="connector in showExternalWallets ? shownConnectors : []"
:key="connector.uid"
@click="() => login(connector)"
class="block choose-connector"
Expand All @@ -57,7 +71,7 @@
<span>{{ connector.name }}</span>
</Button>
<Button
v-if="wcConnector"
v-if="showExternalWallets && wcConnector"
@click="loginWithSafe"
class="block choose-connector"
>
Expand All @@ -68,7 +82,7 @@
<span>Safe</span>
</Button>
<Button
v-if="inAppConnector"
v-if="showInAppWallet && inAppConnector"
@click="
() => {
emit('connecting')
Expand All @@ -84,6 +98,7 @@
<span>In App</span>
</Button>
<Button
v-if="showExternalWallets"
to="https://ethereum.org/wallets/"
target="_blank"
class="link muted small"
Expand All @@ -95,7 +110,7 @@
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, watch } from 'vue'
import type { Connector } from '@wagmi/vue'
import {
useConnect,
Expand All @@ -110,7 +125,7 @@ import { Button, Icon, Alert, Loading } from '@1001-digital/components'
import EvmMetaMaskQR from './EvmMetaMaskQR.vue'
import EvmWalletConnectWallets from './EvmWalletConnectWallets.vue'
import EvmInAppWalletSetup from './EvmInAppWalletSetup.vue'
import type { EvmConnectEmits } from '../types'
import type { EvmConnectProps, EvmConnectEmits } from '../types'

import coinbaseIcon from '../assets/wallets/coinbase.svg'
import metamaskIcon from '../assets/wallets/metamask.svg'
Expand All @@ -137,6 +152,10 @@ const PRIORITY: Record<string, number> = {
'Base Account': 10,
}

const props = withDefaults(defineProps<EvmConnectProps>(), {
connectorFilter: 'all',
inAppWalletInitialStep: 'choose',
})
const emit = defineEmits<EvmConnectEmits>()

const chainId = useChainId()
Expand All @@ -146,7 +165,10 @@ const { mutateAsync: connectAsync } = useConnect()
const inAppConnector = computed(() =>
connectors.value.find((c) => c.type === 'inAppWallet'),
)
const showInAppSetup = ref(false)
const showExternalWallets = computed(() => props.connectorFilter !== 'in-app')
const showInAppWallet = computed(() => props.connectorFilter !== 'external')
const showInAppSetup = ref(props.connectorFilter === 'in-app')
const inAppSetupKey = ref(0)

const shownConnectors = computed(() => {
const unique = Array.from(
Expand Down Expand Up @@ -273,11 +295,29 @@ const onInAppConnected = () => {
emit('connected')
}

const onInAppBack = () => {
if (props.connectorFilter === 'in-app') {
emit('back')
return
}
showInAppSetup.value = false
}

const reset = () => {
resetConnection()
showInAppSetup.value = false
inAppSetupKey.value += 1
showInAppSetup.value = props.connectorFilter === 'in-app'
}

watch(
() => props.connectorFilter,
(connectorFilter) => {
resetConnection()
inAppSetupKey.value += 1
showInAppSetup.value = connectorFilter === 'in-app'
},
)

defineExpose({ reset })
</script>

Expand Down
3 changes: 3 additions & 0 deletions packages/components.evm/src/components/EvmConnectAuth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
<EvmConnect
v-if="!isConnected"
ref="connectRef"
:connector-filter="connectorFilter"
:in-app-wallet-initial-step="inAppWalletInitialStep"
@connecting="emit('connecting')"
@connected="onConnected"
@back="emit('back')"
/>
<EvmSiwe
v-else-if="!isAuthenticated"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@
:resources="resources"
:request-id="requestId"
:expiration-time="expirationTime"
:connector-filter="connectorFilter"
:in-app-wallet-initial-step="inAppWalletInitialStep"
@authenticated="onAuthenticated"
@error="(e) => emit('error', e)"
@back="emit('back')"
>
<template #error-actions>
<slot name="error-actions" />
Expand Down
3 changes: 3 additions & 0 deletions packages/components.evm/src/components/EvmConnectDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
>
<EvmConnect
ref="connectRef"
:connector-filter="connectorFilter"
:in-app-wallet-initial-step="inAppWalletInitialStep"
@connected="onConnected"
@back="emit('back')"
/>
</Dialog>
</template>
Expand Down
Loading
Loading