WalletConnect Integration

The Openloop SDK supports remote device access over WalletConnect v2 in two ways.

The Two Approaches

Approach Level Description
WcTransport Low-level Relays APDU commands through the WalletConnect relay
AppKit High-level Standard multi-chain WC connection via Reown AppKit

WcTransport (APDU Relay)

WcTransport implements ITransport and sends APDU commands to a remote Openloop Connect app through the WalletConnect v2 relay.

How It Works

Diagram 0

Hex-encoded APDUs are sent and received over the custom JSON-RPC method openloop_exchange.

Setup

import { WcTransport, EthereumApp, DEFAULT_ETH_PATH } from '@openloop/sdk-core'
import SignClient from '@walletconnect/sign-client'

// Initialize the WalletConnect SignClient
const client = await SignClient.init({
  projectId: 'your-project-id',
})

// Pair and establish a session
const { uri, approval } = await client.connect({
  requiredNamespaces: {
    eip155: {
      methods: ['openloop_exchange', 'openloop_lock', 'openloop_unlock'],
      chains: ['eip155:1'],
      events: [],
    },
  },
})

// Show the URI as a QR code → the user scans it with Openloop Connect
console.log('Pairing URI:', uri)

// Wait for session approval
const session = await approval()

// Create the WcTransport
const transport = new WcTransport({
  client,
  session: { topic: session.topic },
  chainId: 'eip155:1', // optional (default: 'eip155:1')
})

await transport.open()

// The regular App classes work as-is
const eth = new EthereumApp(transport)
const { address } = await eth.getAddress(DEFAULT_ETH_PATH)

WcTransport Type Definitions

interface WcTransportOptions {
  client: IWcSignClient   // WalletConnect SignClient instance
  session: WcSession       // Active session { topic: string }
  chainId?: string         // Chain ID (default: "eip155:1")
}

interface IWcSignClient {
  request<T>(params: {
    topic: string
    chainId: string
    request: { method: string; params: unknown }
  }): Promise<T>
}

interface WcSession {
  topic: string
}

Supported Methods

Method Description
openloop_exchange Send and receive APDU commands
openloop_lock Acquire the exclusive device lock
openloop_unlock Release the exclusive device lock

Key Features


AppKit (High-Level WC)

With Reown AppKit you sign using WalletConnect’s standard methods — eth_signTransaction, personal_sign, and so on. This approach never handles APDUs directly; Openloop Connect drives the device internally.

Supported Chains and Methods

EVM (eip155)

Chains: - eip155:1 — Ethereum Mainnet - eip155:11155111 — Sepolia Testnet

Methods: | Method | Description | |———|——| | eth_sendTransaction | Send a transaction | | eth_signTransaction | Sign a transaction | | eth_sign | Sign data | | personal_sign | Sign an EIP-191 message | | eth_signTypedData | Sign EIP-712 typed data | | eth_signTypedData_v3 | EIP-712 v3 | | eth_signTypedData_v4 | EIP-712 v4 | | wallet_getCapabilities | Get the wallet’s capabilities |

Bitcoin (bip122)

Chains (SUPPORTED_BIP122_CHAINS in @openloop/sdk-core): - bip122:000000000019d6689c085ae165831e93 — Bitcoin Mainnet - bip122:000000000933ea01ad0ee984209779ba — Bitcoin Testnet3

Methods (SUPPORTED_BIP122_METHODS): | Method | Description | |———|——| | signPsbt | Sign a PSBT | | getAccountAddresses | Get the account addresses | | signMessage | Sign a message (BIP-137) | | sendTransfer | Send a transfer |

import { SUPPORTED_BIP122_CHAINS, SUPPORTED_BIP122_METHODS } from '@openloop/sdk-core'

console.log(SUPPORTED_BIP122_CHAINS.length, SUPPORTED_BIP122_METHODS.join(', '))
// 2 "signPsbt, getAccountAddresses, signMessage, sendTransfer"
Handling the results with SDK functions

Openloop Connect returns the signed PSBT (psbt in the signPsbt result) and the BIP-137 signature (signature in the signMessage result) as base64. The SDK functions take hex, so convert before passing them in.

import type { BtcMessageSignature } from '@openloop/sdk-core'
import { extractRawTx } from '@openloop/sdk-core/bitcoin'
import { verifyBtcMessage } from '@openloop/sdk-core/verify'

function base64ToBytes(b64: string): Uint8Array {
  return Uint8Array.from(atob(b64), c => c.charCodeAt(0))
}
function toHex(bytes: Uint8Array): string {
  return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('')
}

// Result of signPsbt → raw transaction ready to broadcast
function rawTxFromSignPsbt(result: { psbt: string }) {
  return extractRawTx(toHex(base64ToBytes(result.psbt))) // { rawTxHex, txid }
}

// Result of signMessage → verify the BIP-137 signature
function verifySignMessage(message: string, result: { address: string; signature: string }) {
  const bytes = base64ToBytes(result.signature) // [v(1)][r(32)][s(32)]
  const hex = toHex(bytes)
  const sig: BtcMessageSignature = { v: bytes[0], r: hex.slice(2, 66), s: hex.slice(66, 130), signature: result.signature }
  return verifyBtcMessage(message, sig, result.address)
}

The range of v is the same as in BitcoinApp.signMessage (31 to 42, depending on the address type). For the details of extractRawTx and verifyBtcMessage, see the Bitcoin API.

▲ signMessage in Reown’s Bitcoin adapter (checked with @reown/appkit-adapter-bitcoin 1.8.19) reads the signature as hex, so it returns an empty result for the base64 signature above. For signing, call the underlying WalletConnect provider.request({ method: 'signMessage', params: { message, account, address } }, chainId) directly.

Solana

Chains: - solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp — Mainnet Beta - solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 — Devnet

Methods: | Method | Description | |———|——| | solana_signTransaction | Sign a transaction | | solana_signAllTransactions | Sign multiple transactions | | solana_signAndSendTransaction | Sign and send | | solana_signMessage | Sign a message |

TRON

Chains: - tron:0x2b6653dc — TRON Mainnet - tron:0x94a9059e — TRON Shasta Testnet

Methods: | Method | Description | |———|——| | tron_signTransaction | Sign a transaction | | tron_signMessage | Sign a message |

AppKit Configuration Example

import { createAppKit } from '@reown/appkit/react'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
import { SolanaAdapter } from '@reown/appkit-adapter-solana'
import { BitcoinAdapter } from '@reown/appkit-adapter-bitcoin'
import { TronAdapter } from '@reown/appkit-adapter-tron'
import { mainnet, sepolia, bitcoin, bitcoinTestnet } from '@reown/appkit/networks'

const projectId = 'your-walletconnect-project-id'

// Create the adapters
const wagmiAdapter = new WagmiAdapter({ projectId, networks: [mainnet, sepolia] })
const solanaAdapter = new SolanaAdapter()
const bitcoinAdapter = new BitcoinAdapter()
const tronAdapter = new TronAdapter()

// Create the AppKit instance
const modal = createAppKit({
  projectId,
  adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter, tronAdapter],
  networks: [mainnet, sepolia, bitcoin, bitcoinTestnet],
  metadata: {
    name: 'My dApp',
    description: 'My dApp with Openloop',
    url: 'https://my-dapp.com',
    icons: ['https://my-dapp.com/icon.png'],
  },
  customWallets: [
    {
      id: 'openloop-connect',
      name: 'Openloop Connect',
      homepage: 'https://crypto.haudi.jp/openloop/',
      mobile_link: 'openloop://',
      desktop_link: 'openloop://',
      image_url: 'https://crypto.haudi.jp/openloop/images/logo/openloop-icon.png',
    },
  ],
})

Choosing Between WcTransport and AppKit

Criterion WcTransport AppKit
APDU-level control ✓ Full control ✗ Abstracted away
Ease of adoption Somewhat involved Easy
UI components None (build your own) Modal included
Multi-chain Manual switching Automatic switching
EIP-7702 ✓ Supported ✗ Not supported
Supported wallets Openloop Connect only Every WC-compatible wallet

Recommendation: - Integrating with the Openloop device is the main goal → WcTransport - You want to support multiple wallets → AppKit


Limitations

Next Steps