Interacting with a DApp Using a Wallet

·

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:

FieldDescription
nameDApp name
descriptionDApp functionality
urlDApp website
iconsDApp 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:

FieldDescription
bsv:livenetIndicates mainnet or testnet usage
aliasPaymail address
domainDomain associated with the account
addressWallet address
signatureSignature 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:

FieldDescription
addressWallet address
messageMessage to be signed
await wc.signMessage({ address: data.address, message: data.message });

Response:

FieldDescription
signatureSigned 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:

FieldDescription
toRecipient address (formatted based on 'format')
formatType of recipient identifier
amountAmount to send
await wc.sendTransaction({
  outputs: [
    {
      to: data.toAddress,
      format: data.format ? (data.format as any).value.toString() : undefined,
      amount: data.amount.toString(),
    },
  ],
});

Response:

FieldDescription
txIdTransaction ID
timeTransaction timestamp
feeTransaction fee
amountTransferred 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:

FieldDescription
rawHexRaw transaction hex
const res = await wc.sendRawTransaction([rawHex]);

Response:

FieldDescription
txIdTransaction 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:

FieldDescription
prevTxIdPrevious transaction ID
outputIndexOutput index
satoshisAmount in satoshis
lockingScriptLocking script hex

ITransaction.outputs:

FieldDescription
toRecipient script hex
formatSet to 'script'
amountAmount to transfer

signRequests Array:

FieldDescription
inputIndexIndex of the input to sign
addressBitcoin address for signing
sigtypeSignature 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.

FieldDescription
prevTxIdPrevious transaction ID
outputIndexOutput index
satoshisAmount in satoshis
sequenceNumberSequence number
signatureSignature data
pubkeyPublic key
sigtypeSignature type
inputIndexInput index

Handling Errors

During interactions, various errors may occur. Understanding common error codes helps in troubleshooting and improving user experience.

Error CodeDescription
Error: User rejected the requestWallet user declined the authorization request
Error: UnknownAddressBitcoin address does not meet规范
Error: Session not approvedWallet refused to authorize the login session
Error: JSON-RPC Request timeout after 300sWallet 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.