Wallet Connect
Sign-In With Ethereum for Bicrypto — a self-custody wallet becomes a login method. What the addon does, the three things it deliberately does not do, and the one user field it mirrors.
Wallet Connect adds one thing to Bicrypto: a second way to prove who you are. Instead of an email address and a password, a user proves control of an Ethereum address by signing a short message with it, and the platform issues the same session it would have issued for a password login.
That is the whole product. It is an authentication method, not a wallet, not custody, and not a payment path. Everything below is either a consequence of that sentence or a limit implied by it.
The vendor renamed itself. The dependency is @reown/appkit, the dashboard is
cloud.reown.com, and the environment variable is still called
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID. All three refer to the same account.
What it requires
Wallet Connect is an addon. It needs a working Bicrypto install — the core platform, its database, its Redis — and nothing else.
| Requirement | Needed for | If it is missing |
|---|---|---|
| Bicrypto core | Everything | The extension cannot be enabled |
| Redis | Single-use sign-in nonces | Already mandatory for the core. Without it, sign-in refuses rather than degrades |
| A Reown project ID | The wallet picker, and server-side signature verification | The wallet picker still opens; sign-in returns 500 |
| Outbound HTTPS from the backend | rpc.walletconnect.org, where signatures are verified |
Every signature fails verification and every sign-in returns 401 |
It does not require Ecosystem. The two products touch entirely separate things: Ecosystem derives and controls custodial deposit addresses on your install's behalf, and Wallet Connect never sees a private key at all. An install can run either, both, or neither.
What it actually adds
A wallet button on the login modal. With the extension enabled, the sign-in
form offers Sign in with wallet alongside email and password. The button is
rendered only when /api/settings reports wallet_connect among the enabled
extensions, and the backend enforces the same check independently.
A Wallet tab on the user profile, at /user/profile?tab=wallet. This is
where a signed-in user links an address to the account they already have, and
where they unlink it again.
Three endpoints. A nonce issuer, a sign-in verifier and a linker. There is
no admin API, no cron job, no database table of its own beyond the shared
provider_user table that Google sign-in also uses.
No admin section. The extension adds no admin screen at all, because there is nothing to configure there. The entire configuration is one environment variable and the extension toggle.
What it does not do
These come up constantly, and each one is a design decision rather than an oversight.
It does not create accounts. A wallet that is not already linked to a user is refused with "Wallet address not recognized" (401). Registration is still email and password, or Google. A new user therefore signs up conventionally first, then links a wallet, and only then can sign in with it. See Linking a wallet.
It does not hold funds or move them. The only thing a user's wallet is ever asked to do is sign a plain-text message. No transaction is ever proposed, no allowance is requested, and no balance is read. Deposits, withdrawals and on-chain custody are Ecosystem.
It is EVM-only. SIWE messages are eip155 — the verification path is an
Ethereum personal_sign recovery, extended to smart-contract wallets via
EIP-1271. Bitcoin, Solana, Tron, TON and Monero wallets cannot sign in, even on
an install that supports those chains for deposits.
It does not replace a second factor. Wallet sign-in runs through the same 2FA gate as password login. Read the caveat in The sign-in flow before you enable both — the wallet form does not currently render the challenge it can receive.
The one field it mirrors
Linking writes a row into provider_user. When that row is the account's
primary WALLET link, the afterSave hook in
backend/models/access/providerUser.ts copies its address onto
user.walletAddress and the literal string WALLETCONNECT onto
user.walletProvider. Nothing else may write the address: beforeUpdate and
beforeBulkUpdate in backend/models/user.ts throw "user.walletAddress is a
mirror of providerUser and may only be changed by the SIWE link flow" unless
the caller passes context: { source: "providerUserMirror" }, which is exactly
what that hook passes and what no handler, admin form or API call does. Read
that as covering walletAddress and not the pair — both guards test that one
attribute by name, so a write touching only walletProvider is never
challenged.
The guard is there because the column is money. NFT auction settle and NFT
listing buy read user.walletAddress as a payout target, so any writer that
could set it could point a settlement at an address nobody ever proved they
control. Tying it to provider_user means every value in it was signed for.
The products that read the column therefore work for a user who has linked a wallet, and only for them:
- NFT Marketplace allows its on-chain actions. Buying a listing,
transferring a token, confirming an offer and approving a contract all call a
helper in
nft/utils/nft-auth.tsthat readsuser.walletAddressand throws "Connect a wallet address in your profile to …" when it is empty. A user who has linked a wallet passes it. A user who has not still gets that message, and linking one is the answer. See NFT Marketplace. - Profile completion can reach 100%. The completion score counts ten fields
and one of them is
walletAddress, so an account with no linked wallet caps at 90%. - The profile dashboard card and the Wallet tab agree. The card reads
user.walletAddress, the tab reads theprovidersarray, and for a linked primary address both show it.
Two limits before you build anything on the column. The mirror holds the
primary link only, so a user with two linked wallets has two provider_user
rows and one user.walletAddress — any count or export off the user row
undercounts. And the guard is a Sequelize hook, not a database trigger, so a
direct SQL UPDATE bypasses it in silence — as does any Sequelize call passing
hooks: false, which is how the mirror hook itself gets through. Neither
bypass reports anything. Treat provider_user as the source
of truth in anything you write yourself. Which link is primary, what promotion
does on unlink, and the repair statement are in
Linking a wallet.
Where to start
The Reown project, the one environment variable that is not in
.env.example, and why a frontend rebuild is not optional.
Nonce, signature, session — what the backend checks, what it does not check, and the rate limit that stops at two attempts.
The Wallet tab, the provider_user row it writes, unlinking, and the
walletAddress mirror in full.
The chain allow-list on both sides, every endpoint and every variable the addon reads.
The failures that look like something else — a 500 that means a missing variable, and a success toast that means no session.
Before you enable it
Three facts worth knowing on day one.
The project ID is read in two processes, in two different ways. The frontend
inlines it at build time because the name starts with NEXT_PUBLIC_; the
backend reads it once at module load. Changing it needs a frontend rebuild and
a backend restart, and neither one alone is enough.
Sign-in is rate limited to five requests per fifteen minutes per IP, shared with every other endpoint on the same limiter. One complete sign-in costs two of those five. Behind a corporate NAT or a shared office address, that budget is shared by everyone on it.
A wallet the user cannot sign with is a locked account. If a linked wallet is the only credential a user has — say they registered through Google and later lost that too — there is no self-service recovery for a lost seed phrase. Password reset by email remains the fallback for any account that has an email address, which is all of them.