Interacting with decentralized applications (DApps) is a core part of the blockchain experience. Whether you're making payments, signing messages, or executing transactions, understanding how to connect and communicate with a wallet is essential. This guide walks you through the key processes involved in DApp and wallet interactions, using industry-standard protocols and methods.
Understanding the Basics
DApps are applications that run on decentralized networks, often leveraging blockchain technology for security and transparency. To perform actions like sending transactions or signing data, these applications need to interact with a user's cryptocurrency wallet. This interaction is typically facilitated through established protocols like WalletConnect, which ensures secure and standardized communication.
The process generally involves initializing a connection, authorizing actions, and handling various transaction types. Each step requires specific data structures and method calls to ensure smooth operation. By following standardized practices, developers can create seamless experiences for users.
Establishing a Connection
Before any interaction can occur, the DApp must establish a connection with the user's wallet. This is done using the WalletConnect protocol, which acts as a bridge between the DApp and the wallet.
Initialization
The first step is initializing the WalletConnect client. This involves setting up the relay server URL, which handles the communication between the DApp and the wallet. Developers can use their own server or rely on the official WalletConnect infrastructure.
await WalletConnectClient.init(
  Object.assign(
    {
      relayUrl: 'wss://domain', // Specify your server or use WalletConnect's
    } as ClientOptions,
    opts ?? {}
  )
);Connecting to the Server
Once initialized, the DApp uses the connect() method to establish a connection with the WalletConnect server. This method requires providing details about the DApp, such as its name, description, website URL, and icons.
DApp Data Structure:
| Field | Description | 
|---|---|
| name | DApp name | 
| description | DApp functionality | 
| url | DApp website | 
| icons | DApp icon | 
const wc = useWallet();
await wc.connect(DAPP);Handling the Response
After a successful connection, the wallet returns session data containing account information. This includes details like the network type, paymail alias, domain, address, and a signature for verification.
Response Structure:
| Field | Description | 
|---|---|
| bsv:livenet | Indicates mainnet or testnet usage | 
| alias | Paymail address | 
| domain | Domain associated with the account | 
| address | Wallet address | 
| signature | Signature of the session's public key | 
The response string is formatted with segments separated by specific delimiters. For example: bsv:livenet:alias|domain|address|signature. If no alias is available, the structure adjusts accordingly.
Securing Interactions with Signatures
To ensure that interactions between the DApp and wallet are secure, message signing is used. This process verifies that the wallet owner authorizes the actions requested by the DApp.
Signing a Message
The DApp sends a message to the wallet for signing using the signMessage method. The wallet signs the message with its private key and returns the signature.
Input Structure:
| Field | Description | 
|---|---|
| address | Wallet address | 
| message | Message to be signed | 
await wc.signMessage({ address: data.address, message: data.message });Response:
| Field | Description | 
|---|---|
| signature | Signed message data | 
Verifying the Signature
Once the DApp receives the signature, it must verify its authenticity using the wallet's address and the original message.
const result = Message.verify(data.message, data.address, data.sig);This step ensures that the message was indeed signed by the wallet owner and hasn't been tampered with.
👉 Explore more strategies for secure transactions
Executing Transactions
DApps often need to send transactions, whether transferring funds or executing smart contracts. The wallet provides methods to handle both single and batch transactions.
Sending a Single Transaction
For transferring funds to a single recipient, the sendTransaction method is used. It requires specifying the recipient's address, the format (e.g., address, paymail, script), and the amount.
Input Structure:
| Field | Description | 
|---|---|
| to | Recipient address (formatted based on 'format') | 
| format | Type of recipient identifier | 
| amount | Amount to send | 
await wc.sendTransaction({
  outputs: [
    {
      to: data.toAddress,
      format: data.format ? (data.format as any).value.toString() : undefined,
      amount: data.amount.toString(),
    },
  ],
});Response:
| Field | Description | 
|---|---|
| txId | Transaction ID | 
| time | Transaction timestamp | 
| fee | Transaction fee | 
| amount | Transferred amount | 
Sending Multiple Transactions
For batch operations, the sendRawTransaction method allows sending pre-signed transactions. This is useful for complex operations involving multiple steps.
Input Structure:
| Field | Description | 
|---|---|
| rawHex | Raw transaction hex | 
const res = await wc.sendRawTransaction([rawHex]);Response:
| Field | Description | 
|---|---|
| txId | Transaction ID | 
Transaction Signing
In some cases, DApps may need to request signatures for transactions without immediately broadcasting them. The signTransaction method handles this.
Input Data Structure
The method requires details about the transaction inputs and outputs, as well as signing requests.
ITransaction.inputs:
| Field | Description | 
|---|---|
| prevTxId | Previous transaction ID | 
| outputIndex | Output index | 
| satoshis | Amount in satoshis | 
| lockingScript | Locking script hex | 
ITransaction.outputs:
| Field | Description | 
|---|---|
| to | Recipient script hex | 
| format | Set to 'script' | 
| amount | Amount to transfer | 
signRequests Array:
| Field | Description | 
|---|---|
| inputIndex | Index of the input to sign | 
| address | Bitcoin address for signing | 
| sigtype | Signature type flags | 
const transaction: ITransaction = {
  inputs: [
    {
      prevTxId: data.prevTxId,
      outputIndex: data.outputIndex,
      satoshis: data.satoshis,
      lockingScript: data.lockingScriptHex,
    },
  ],
  outputs: [
    {
      to: script.toHex(),
      format: 'script',
      amount: String(data.outputSatoshis),
    },
  ],
};
const request: ISignTransaction = {
  transaction,
  signRequests: [
    {
      inputIndex: 0,
      address: data.address,
      sigtype: bsv.crypto.Signature.SIGHASH_ANYONECANPAY | bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID,
    },
  ],
};
await wc.signTransaction(request);Response Data Structure
The response includes an array of signatures, each containing details about the signed input.
| Field | Description | 
|---|---|
| prevTxId | Previous transaction ID | 
| outputIndex | Output index | 
| satoshis | Amount in satoshis | 
| sequenceNumber | Sequence number | 
| signature | Signature data | 
| pubkey | Public key | 
| sigtype | Signature type | 
| inputIndex | Input index | 
Handling Errors
During interactions, various errors may occur. Understanding common error codes helps in troubleshooting and improving user experience.
| Error Code | Description | 
|---|---|
| Error: User rejected the request | Wallet user declined the authorization request | 
| Error: UnknownAddress | Bitcoin address does not meet规范 | 
| Error: Session not approved | Wallet refused to authorize the login session | 
| Error: JSON-RPC Request timeout after 300s | Wallet did not respond to the DApp's request within the timeout period | 
👉 Get advanced methods for error handling
Frequently Asked Questions
What is WalletConnect?
WalletConnect is an open-source protocol that enables secure connections between DApps and wallets. It uses relay servers to facilitate communication, ensuring that sensitive data like private keys never leave the user's device.
Why is message signing important?
Message signing verifies that the wallet owner authorizes specific actions, such as transactions or login attempts. It adds a layer of security by proving ownership without exposing private keys.
Can I use multiple wallets with one DApp?
Yes, most DApps support connecting to multiple wallets. However, each connection is managed separately, and users must authorize each wallet individually.
What should I do if a transaction fails?
First, check the error message returned by the wallet. Common issues include insufficient funds, incorrect addresses, or network congestion. Ensure all input data is correct and try again.
How are transaction fees determined?
Transaction fees depend on network congestion and the complexity of the transaction. Wallets often calculate fees automatically, but users can sometimes adjust them based on urgency.
Is there a way to speed up a slow transaction?
If a transaction is taking too long, it might be due to low fee settings. Some wallets allow replacing the transaction with a higher fee, but this isn't always supported.