Universal Wallet Uplink (UWULink) is a protocol that allows applications (dApps) to request a wallet to make a batch of contract calls in a single atomic transaction without establishing a two-way connection or revealing the user's address to the requester. The protocol defines a compact binary message format using Protocol Buffers suitable for low-bandwidth channels such as QR codes or NFC tags. Two modes of operation are supported: Static Mode, where the request payload directly contains the list of calls, and Programmable Mode, where the payload references a contract that generates the list of calls.
dApps often require users to perform multistep interactions, such as approving a token and then executing a
swap. Traditionally, accomplishing this has required multiple user confirmations or complex wallet connectivity.
Recent standards like EIP-5792 and EIP-7702 introduced ways to batch multiple calls into one atomic operation
via JSON-RPC (e.g. wallet_sendCalls
). However, current implementations of those solutions assume an active connection
between dApp and wallet (e.g. injected provider or WalletConnect session), which creates an additional layer of UX friction.
There is a growing need for a privacy-preserving, frictionless workflow where a dApp or product can trigger complex
transactions without “connecting” their wallet or needing to disclosing their address to the application.
Several trends highlight this need:
Atomic Multi-Call Transactions: With the advent of account abstraction features like EIP-7702, externally owned accounts (EOAs) can execute batch transactions a la smart contract wallets. Users expect to combine multiple actions (e.g. token approvals, swaps, transfers) into one confirmed transaction for better UX and guaranteed all-or-nothing execution. Developers likewise want to avoid partial failures or multiple prompts.
Privacy Concerns: Current wallet connection flows (EIP-1193 / EIP-1102) require dApps to request access to user accounts, linking the user's address with the dApp even before a transaction is made. By decoupling transaction construction from user identification, we improve privacy. The wallet should not need to announce “who” it is to the dApp just to receive a transaction request. A one-way communication means the dApp never learns the user's address or other account info, mitigating tracking and profiling risks.
One-Way Offline Interaction: In many use cases (desktop-to-mobile workflows, point-of-sale terminals, printed media), it’s desirable to communicate a transaction request via a QR code, NFC tag, or URL without establishing a session. Protocols like WalletConnect provide a session-based two-way link, but are heavyweight when a simple one-off action is needed, and they reveal the user's account to the dApp. A unidirectional link allows, for example, a user to scan a QR code on a webpage or poster and complete an on-chain action entirely within their wallet app. This also enables fully offline dApps to hand off transactions securely to user devices.
Compactness for QR/NFC: Encoding transaction data for use in QR codes or NFC imposes strict size limits. Prior standards (e.g. EIP-681 Ethereum URIs) used human-readable formats that become lengthy when including contract data (hex-encoded addresses and calldata inflate the size). WalletConnect addressed some issues by introducing a more efficient URI scheme (ERC-1328) instead of embedding JSON in QR codes. UWULink builds on this principle by using a concise binary serialization (Protocol Buffers), allowing more data to be communicated in a QR code or NFC tap.
Programmability and Offloading Logic: There are scenarios where the exact list of calls depends on on-chain state or user-specific data (for example, an airdrop claim that needs to gather all tokens claimable by that user, or a DeFi interaction where allowances may already exist). In such cases, encoding all call data statically could be unwieldy or inefficient if the dApp lacks knowledge of the user's address. UWULink’s Programmable Mode allows a dApp to redirect to a deterministic on-chain generator (a smart contract with a standard interface) that can produce the list of calls that the wallet should invoke. This allows the app to invoke arbitrary logic in generating the list of calls based on the state of the blockchain, still without exposing the user's address.
By addressing these points, UWULink aims to enhance user experience with one-shot multi-call transactions and improve security and privacy by eliminating unnecessary data sharing.
UWULink defines a protobuf message format and interpretation for transaction requests sent from a dApp to a wallet. The only operation in scope is a request for the wallet to execute an atomic batch of contract calls on an EVM-compatible blockchain. The wallet, upon receiving a UWULink request (for example, via a QR code scan, deep link, or NFC), will decode it, present the details to the user for confirmation, and if approved, execute the calls as a single transaction on the specified chain.
Key characteristics of the protocol:
Unidirectional Communication: Communication is only dApp → wallet. The dApp encodes a request and the wallet handles it. There is no handshake or return channel in the protocol itself. The wallet is not required (or able) to send any data back to the dApp. This one-way design ensures the dApp does not learn any information about the user or wallet (such as the address or which wallet app is used) at request time. It also simplifies implementation – the dApp’s job is simply to generate a batch of calls and display it, and the wallet’s job is to execute or reject the batch.
Atomic Batch Calls: All calls listed in the request MUST be executed atomically, i.e. all succeed or all fail together. If any specific call would revert, the wallet should revert the entire batch.
No Identity / Auth Required: Because the request is self-contained, the wallet does not need to pre-authorize the
dApp or reveal the selected account. In traditional injected scenarios, a dApp would call eth_requestAccounts
(as
per EIP-1102) to get the user's address. With UWULink, the first and only interaction is the user willingly
importing the transaction request (e.g. scanning the QR). The wallet should treat it similarly to how it would treat
a transaction payload from a connected dApp, except no connection context exists. If the request is malformed
or not supported, the wallet can simply alert the user and refuse.
Binary Encoding: UWULink messages are encoded as a binary blob based on a Protocol Buffers schema. This blob is
then further encoded into a URI for transport via QR code or NFC tag. The string encoding of a request is the
uwulink:
scheme followed by the base 64 encoded message:
uwulink:<base64_of_UWULink_message>
Chain Identification: The message includes a chain ID to indicate which chain the calls are intended for. The
wallet must verify or use this chain ID when executing the transaction.
If the wallet is not currently on the target chain, it SHOULD prompt the user to switch to that chain or automatically
switch if permissible (similar to handling of chainId
in EIP-681 URIs). If the wallet cannot operate on the
requested chain, it must reject the request. This ensures that the dApp’s intent (which chain’s contracts to interact
with) is preserved and avoids confusion if the user is on a different network.
Static vs Programmable Mode: There are two mutually exclusive ways to specify the batch of calls:
Static Mode: The request directly contains the list of calls (each with target address, calldata, and
optionally ETH value) that the wallet should execute in order. This mode is straightforward and similar to
existing multi-call APIs (e.g. the JSON-RPC wallet_sendCalls
payload from EIP-5792), but encoded in a
compact binary form. This is ideal when the dApp knows exactly what actions need to be performed.
eth_call
) to retrieve
the actual list of calls to execute. The resolver contract must implement a standardized interface (detailed
below) that takes the provided input (and possibly the caller’s address or other context) and returns a set of
calls. This mode allows dynamic computation of call lists at execution time. It improves flexibility (the dApp
can offload complex logic or personalization to the blockchain) and keeps the QR/NFC payload small since it only
carries a contract address and input, rather than every call’s details. For example, a dApp could include just a
reference like “resolver contract X with input Y” and that contract’s resolver function will output perhaps
dozens of calls based on the latest on-chain state, the user's address, etc.The UWULink message includes a oneof/union to indicate which mode is used. Wallets SHOULD support both modes.
Below is the proposed Protocol Buffers v3 schema defining the UWULink message format:
syntax = "proto3";
package org.ethereum.uwulink;
// The top-level UWULink transaction request message.
message UWULinkRequest {
uint64 chain_id = 1; // EIP-155 chain ID for the target chain
oneof request_type {
Batch batch = 2;
ResolverReference resolver = 3;
}
}
// Static batch of calls
message Batch {
repeated Call calls = 1;
}
// Single contract call
message Call {
bytes to = 1; // 20-byte address of target contract
optional bytes value = 2; // (optional) up to 32-byte big-endian ETH value
optional bytes data = 3; // (optional) calldata for the call
}
// Reference to a resolver contract for dynamic call generation
message ResolverReference {
bytes resolver_address = 1; // 20-byte address of resolver contract
bytes resolver_data = 2; // opaque data to pass to resolver
}
Notes on the schema:
We use bytes
for addresses and other binary data. A conforming wallet implementation MUST enforce that Call.to
and
ResolverReference.resolver_address
are exactly 20 bytes. The protobuf itself won't enforce length, but using a
different length should cause the wallet to reject the message (to avoid ambiguity or mis-interpretation). The value
field in Call
can be 0 bytes (interpreted as 0 ETH) up to 32 bytes. Leading zeros in value
SHOULD be stripped in
the encoding for consistency (e.g., 1 wei would be encoded as 0x01
not 32 bytes padded; conversely the decoder
should treat a missing value
or empty value
as 0).
All fields are numbered for efficient encoding. The oneof request_type
ensures only one of Batch or
ResolverReference is in use. If an unknown field is present (e.g., a future extension), the wallet should ignore those
unknown fields per protobuf default behavior, but core fields must be present for validity (chain_id and one of
batch/resolver).
If using a text encoding (like Base64) to embed in a URI, the entire UWULinkRequest
message is serialized to a
binary string, then that binary is base64-encoded. For URI safety, base64 output may need to be URL-encoded (i.e.,
+
, /
characters percent-encoded or using the URL-safe base64 variant). This is an implementation detail, but
wallet developers should be aware when parsing input. In all cases, the underlying data after decoding is expected to
match the protobuf schema above.
The schema is chosen for broad compatibility. Proto3's varint encoding will handle the chain_id
(which is usually
small like 1, 137, etc.) in 1-2 bytes. The calls
repeated field will simply concatenate call entries. Each call
entry will have a 1-byte field tag for to
followed by 20 bytes address, etc. This results in a very compact
representation.
Example of an encoded message (for illustration): A static request for chain 1 with two calls might look like:
Call 1: to = 0x111111...1111
, value = none (0), data = 0xabcdef
0x222222...2222
, value = 100 wei, data = (empty)ethereum:uwulink?request=EiABAggDEhARERERERERERERERERERERERERERIAGKDCr+8=
(this is a fake example string for
concept; actual encoding would differ).Wallet and dApp developers can import this .proto
to ensure they are constructing and parsing UWULink messages
consistently.
This standard introduces an interface that resolver contracts must implement so that wallets can query them for call
batches. All resolver contracts MUST implement the following ABI (interface identifier UWUResolver
):
/// @title UWULink Resolver Interface
interface UWUResolver {
struct Call {
address target;
uint256 value;
bytes data;
}
// Thrown when calls could not be generated, with an error code specific to this resolver.
error CallGenerationFailure(uint256 errorCode);
/**
* @notice Compute a batch of calls for a given request.
* @param requester The address of the wallet (EOA or contract) that is requesting the calls.
* @param data Arbitrary request data (opaque to the wallet, provided by dApp via UWULink).
* @return calls The list of calls that correspond to the requester and request
*/
function getCalls(address requester, bytes calldata data) external returns (Call[] memory calls);
/**
* @notice Returns the details for the given error code. Meant to be called by developers to better understand the error code for a resolver.
* Due to localization needs, it is expected that developers may call this function, but the wallet should not show this information to users.
*/
function getErrorCodeDetails(uint256 errorCode) external returns (string memory information);
}
requester
is included to allow the contract to tailor results to the specific user. For example, a resolver
could check requester
’s token holdings or permissions and then return different call sets. The wallet should supply
its own sending address as requester
. This means that the user’s address is revealed only to the RPC server used
by the wallet via this call, not to the dApp server or UI. In the future with the propagation of light clients, it's
possible for the wallet to avoid revealing this information.data
parameter is the exact bytes provided in the UWULink request’s resolver_data
. Its contents and encoding
are defined by the dApp’s usage and the contract’s logic. For instance, it might contain an enum indicating which
action to perform, or some user-specific claim ID, etc.Wallets should implement the following logic for programmable requests:
eth_call
to resolver_address
with to = resolver_address
, from = address(0)
,
data = ABIEncodeWithSelector(UWUResolver.getCalls, userAddress, resolver_data)
against the latest block. The wallet
MAY use the pending block, or otherwise include transactions in the state that are yet to be included in a
confirmed block.The dApp developer and resolver contract developer are responsible for ensuring that calling getCalls
is not too
gas-intensive to execute (since wallets will execute it off-chain but it still must complete execution). Excessive
computation could result in the node returning an error (out of gas exception in the eth_call context). Typically these
functions will just gather data from known contracts or encode some predefined calls, which should not be prohibitively
expensive.
To illustrate how UWULink can be used in practice, consider the following scenarios:
1. Static Mode – Token Approval and Swap (DeFi use-case):
Alice wants to trade tokens on a decentralized exchange (DEX) using her mobile wallet, but she doesn't want to connect
her wallet to the DEX website due to privacy concerns. The DEX dApp prepares a UWULink QR code for the trade. When Alice
selects the tokens and amount on the website, the dApp formulates two contract calls: one to the ERC-20 token contract
to approve()
the DEX's router contract, and one to the router contract to execute the swap (
swapExactTokensForTokens
, for example). Normally this would be two separate transactions with two confirmations.
Instead, the dApp bundles them:
to = TokenContract, data = approve(router, amount)
to = RouterContract, data = swapExactTokensForTokens(params...)
Both calls have value = 0
(no ETH being sent directly). The dApp encodes these into a UWULinkRequest (static mode) for
the current chain (e.g. Ethereum mainnet chain_id 1). The protobuf binary is base64 encoded and placed into a QR code
with a URI like:
uwulink:CgEBEiAx... (truncated)
Alice scans this QR with her wallet app. The wallet decodes the request: chain_id=1, two calls in batch. It recognizes it can execute an atomic batch (Alice’s wallet supports EIP-7702). The wallet UI shows Alice a summary: "This dApp is requesting two actions: (1) Approve Token XYZ for spending, (2) Swap Token XYZ for Token ABC on DEX." Alice can inspect the contract addresses (perhaps the wallet resolves known token/contract names or shows the hex addresses) and the parameters. She sees that both will be submitted together in one transaction. The UI might look similar to a multi-call confirmation screen.
Alice accepts. Her wallet internally either crafts a 0x4 type transaction (since Alice is an EOA on Ethereum) embedding bytecode to do the two calls, or uses its smart wallet module. It then signs and broadcasts the transaction. On-chain, the two calls execute one after the other, and because of atomicity, if the swap were to fail, the approve would be reverted too (avoiding a scenario where she approved tokens without actually swapping).
The DEX backend or frontend can monitor the blockchain for the transaction receipt (it knows what actions it expected, or Alice can manually input the tx hash if needed). The important part is the DEX never learned Alice’s address beforehand; it only sees it when the transaction hits the blockchain, which is unavoidable for executing the trade but at that point privacy is preserved as well as any normal on-chain interaction (the dApp cannot link it to Alice’s web session unless Alice herself tells it out-of-band). This shows how UWULink achieves one-scan confirmation for what used to be multi-step, and keeps Alice’s identity private until the on-chain execution.
2. Programmable Mode – Personalized Airdrop Claim:
A project is running an airdrop where eligible users can claim several different token rewards based on on-chain activity. Bob visits the airdrop dApp page. The page could ask Bob to connect his wallet to figure out what he’s eligible for, but Bob is cautious. Instead, the dApp uses UWULink in programmable mode. It has a resolver contract deployed on-chain which, given a user address, can determine all the reward token contracts and amounts that the user is entitled to claim.
The dApp shows Bob a “Claim Rewards” button, which reveals a QR code. This QR encodes a UWULink request with:
chain_id = 5
(Goerli testnet, for example, where the airdrop is happening).resolver_address = 0xDeeD…1234
(the address of the AirdropResolver contract).resolver_data =
some bytes encoding maybe an airdrop campaign identifier or simply empty if one global campaign.Bob scans this with his wallet. The wallet sees it's a resolver-type request. It calls
getBatchCalls(BobAddress, resolver_data)
on 0xDeeD...1234
(as a view call). The AirdropResolver contract looks up
internally that BobAddress is eligible for 3 tokens: TokenA, TokenB, and TokenC with certain amounts, and the claim
function for each is claim(address claimant, uint256 amount)
on each token’s distributor contract. It returns three
arrays: targets = [AddrA, AddrB, AddrC]
, values = [0,0,0]
(no ETH needed), callData =
[ abi.encodeWithSelector(Distributor.claim, Bob, amtA), ... ]
for each token.
The wallet receives these arrays. It now has three calls to execute. It shows Bob: "Claim TokenA: amount X, Claim
TokenB: amount Y, Claim TokenC: amount Z" (assuming the wallet can decode the function signatures or at least show
contract addresses and method names if it has ABIs). Bob approves the batch. The wallet then either directly calls each
distributor’s claim
in one aggregated transaction. Because Bob’s address was provided to the resolver, each claim call
will credit tokens to Bob (likely the contract uses the provided address or msg.sender
– here it was likely coded to
use the address parameter, since the actual transaction sender will be Bob’s own address in the batch execution
context). The important part is Bob did not have to connect his wallet to the dApp; the eligibility and calls were
determined by the on-chain contract. The dApp never saw Bob’s address, yet Bob gets his tokens in one go.
After execution, Bob’s wallet shows the transaction success. The dApp might simply tell him to check his balances (or it could have a public page showing which addresses claimed, etc., but it did not get a direct notification — it relies on Bob or the blockchain to know the claim happened).
3. Cross-Device Payment via NFC (Point of Sale):
Carol is at a merchant's point-of-sale device that accepts cryptocurrency payments via Ethereum. The merchant’s device can display a QR or emit an NFC message with a payment request. Instead of using a simple one-address payment URI (as in EIP-681), the merchant uses UWULink to request a more sophisticated transaction: perhaps Carol will pay through a specific escrow contract or with a certain token if she has a discount coupon.
The device sends an NFC payload which Carol’s phone picks up (many wallet apps can register as handlers for certain NDEF messages or custom URI schemes). The payload contains a UWULinkRequest in static mode:
transfer(merchantAddress, amount)
(to pay the merchant), second
call to a logging contract registerPurchase(merchantId, CarolAddress, amount)
(to log the sale in an on-chain
registry). Both calls are value 0 since a token transfer, not ETH, is used.Carol’s wallet opens with the decoded request: It shows "Pay 50 USDC to Merchant XYZ and register purchase." Carol sees the merchant name resolved from the merchant’s address (if her wallet has ENS or a local registry of known merchants). She approves. The wallet then executes an atomic transaction on Polygon that calls the USDC token contract and the registry contract. The merchant’s PoS waits for confirmation on-chain (or simply trust the signed transaction once broadcast, depending on their risk tolerance). Carol’s identity remained pseudonymous; the merchant’s device did not directly get her wallet info, it only received the on-chain payment. And Carol only had to tap once to approve both token transfer and logging, rather than scan one QR to pay then perhaps another to log, etc.
These examples demonstrate the flexibility of UWULink:
TBD
UWULink is an additive protocol and does not break any existing standards. It is designed to coexist with current methods:
Existing Wallet URIs (EIP-681, EIP-831): UWULink can be seen as an evolution of the idea behind EIP-681 (
transaction request URIs). EIP-681 defines URIs for a single transaction (or payment) and is already supported in a
limited number of wallets for QR code scanning. UWULink extends the concept to multiple calls and binary encoding. A
wallet that does not recognize the uwulink:
scheme should simply not act on it. Typically, such
a wallet would either show an error or ignore a scanned QR it cannot parse. This is a graceful failure from the user's
perspective (they'll know the wallet doesn't support that request). There is no risk of confusing an UWULink QR with
an EIP-681 QR, since the scheme and content format differ. Therefore, wallets that only implement support for EIP-681
will not mistakenly handle a UWULink payload as a valid request.
Ensuring Backwards Compatibility in Data Format: The protobuf schema is designed such that new fields could be
added in the future in a non-breaking way (per Proto3 rules, unknown fields are ignored by receivers). For example, a
future version might add an optional uint64 expiration_timestamp
or string origin
field to carry a domain name for
UI display. An older wallet would ignore these and still execute the core request. This forward-compatibility means
UWULink can evolve without breaking older implementations, as long as additions are carefully made optional.
Fall-back to Standard Flows: From a dApp perspective, implementing UWULink does not preclude supporting traditional wallet connections. A dApp can offer UWULink QR codes for users who prefer privacy or are on devices (like a separate mobile) without browser extensions. At the same time, it can have the usual “Connect Wallet” button for users who are okay with that. This multi-modal approach ensures no user is left out. Over time, if UWULink (or similar one-way flows) prove safer and more popular, they might become the default, allowing users to interact with dApps without connecting a wallet.
Network Compatibility: We limit scope to EVM-compatible chains. That means chains that use EIP-155 transaction scheme and Ethereum-like addresses. On non-EVM chains, this standard doesn’t apply (though analogous concepts could). Within EVM chains, a nuance: if a chain has a different maximum gas limit or transaction format peculiarity, the wallet internally deals with that. UWULink just says "execute these calls." As long as the wallet can create a transaction that does so, it’s fine. If an EVM chain does not support atomic multi-call (some L2s or sidechains might not immediately support EIP-7702), the wallet has to handle it at the account abstraction layer if possible, or otherwise MUST reject the request. This again falls to the wallet to know its capabilities (e.g. per EIP-5792’s capabilities query).
In conclusion, UWULink aims to introduce new functionality without disrupting existing user journeys. It is opt-in for all parties. Early adopters (both dApps and wallets) can experiment with it while others continue as usual. As support grows, it could become a widely recognized standard for secure one-way wallet interactions. The design takes into account lessons from previous proposals (EIP-681 URIs, WalletConnect, EIP-5792, etc.) and ensures that adopting UWULink is a low-risk enhancement rather than a breaking change to Ethereum’s ecosystem.
UWULink is designed with privacy in mind, but it introduces some new security aspects that implementers and users should consider:
eth_call
, so it does not create a public transaction. However, the node or RPC
provider that the wallet uses will see that call (just like any read call). If the RPC provider is untrusted, this
could leak some information (e.g., that this address is interested in this resolver's data). In most cases this is a
minor concern (no more revealing than using the dApp itself while connected to an RPC), but users who are extremely
privacy-conscious might prefer static mode or ensure they use a privacy-respecting RPC. Importantly, the dApp backend
or frontend does not see this – only the blockchain infrastructure does.Phishing and Malicious QR Codes: A malicious actor could present a user with a UWULink QR code that, if scanned and approved blindly, could cause the user to transfer funds or approve tokens to the attacker. This risk is analogous to phishing links or malicious dApp websites in today's context. Users should be educated to only approve UWULink requests from sources they trust or understand. Wallets should help by displaying clear human-readable information about what the request will do:
Show the names or ENS of known contract addresses involved (or at least highlight unknown addresses).
Possibly warn if the request involves calling an unrecognized contract with large value transfers or if it sets a high token allowance, etc.
Atomic Execution and Reverts: By enforcing atomic execution, UWULink ensures that partial completion won't lead to
stuck funds or unintended states. However, this also means a malicious or buggy request could be crafted to always
revert (for example, by including an incompatible call), which could waste user gas fees if not caught. Wallets should
simulate the batch when possible. If the wallet can do a dry-run (for instance using eth_call
on a Bundler or
internal simulation) it might detect a guaranteed revert and inform the user that the call set is invalid (though this
might be complex to do reliably for all calls).
Resolver Contract Trust: The programmable mode introduces a potential trust issue: the user is effectively trusting the resolver contract’s code to generate the calls honestly. If the resolver contract is malicious, it could return call data that benefits an attacker. For example, a malicious resolver could ignore the input data and always return a call transferring all of the user's ETH to the attacker’s address. Mitigations:
Ideally, resolver contracts should be open source and verified, and the dApp using them should be reputable. The wallet can’t fully know if the resolver’s output is malicious until it sees it, but the user will have a chance to review the resulting calls anyway. This is crucial: the wallet must display the resulting calls from the resolver to the user, just as it would in static mode. The user should then notice if something is off (e.g., a transfer of all their ETH is about to happen).
We assume resolver contracts will often be provided by the same party as the dApp and thus come with an implied level of trust (or at least, they can be audited by the community if the UWULink scheme becomes popular).
No Automatic Spending: UWULink does not introduce new signing or authorization paradigms – it uses actual transactions that the user signs on the spot. Thus, it’s less prone to the kind of issues where a signature can be later reused (like the risks with off-chain signatures). Each UWULink request is one transaction (with possibly multiple subcalls). After it's executed, the link cannot be reused to automatically trigger more actions (unless the user scans it again). This is good from a security standpoint since it doesn't create long-lived permissions. One exception: if a call within the batch is an approval or something, that is an on-chain permission that persists as usual (the user should be made aware as normal).
Comparison to Traditional Flows: One might ask, does eliminating the wallet <-> dApp handshake create any new risks?
In traditional connected dApp sessions, the wallet at least knows the origin of requests (e.g., which website is calling
eth_sendTransaction
or wallet_sendCalls
). In UWULink, the origin is essentially "the QR code the user scanned" – the
wallet might know the payload came via a QR/NFC but not which app or site. In security terms, this means the wallet
cannot apply domain-based whitelists or blocklists (since there's no domain, unless the URI contains one in the payload
which it typically wouldn't). Therefore, the user must manually trust and verify each request. This is akin to using
a hardware wallet: every transaction is shown on a screen and the user approves it, with no assumptions about where it
came from. This places responsibility on the user and makes the wallet’s job of displaying info accurately even more
important.
Privacy vs. Usability Trade-off: Because UWULink doesn’t let the dApp query the wallet off-chain, some conveniences are lost – e.g., the dApp cannot automatically fetch the user's address to display their balance or NFTs in the UI prior to a transaction. This is a conscious privacy trade-off. Some advanced dApps might find workarounds (like asking the user to input their address manually if they want to see personalized info, or shifting more logic on-chain as in programmable mode). Users and dApp developers must understand this trade-off. In contexts where user personalization without login is needed, UWULink might require a bit more creativity, but it ensures that if the user chooses not to share anything, they truly don't until a transaction is made.
Copyright and related rights waived via CC0.