クイックスタート

Openloop SDK を使ってハードウェアウォレットに接続し、アドレス取得やトランザクション署名を行う手順を説明します。

インストール

コアパッケージと、使用する接続方式の Transport パッケージをインストールします。

# USB 接続(Chrome/Edge デスクトップ)
npm install @openloop/sdk-core @openloop/transport-webhid

# Bluetooth 接続(Chrome/Edge + Android)
npm install @openloop/sdk-core @openloop/transport-webble

# 全ブラウザ対応(Openloop Connect 経由)
npm install @openloop/sdk-core @openloop/transport-local

# iOS Safari
npm install @openloop/sdk-core @openloop/transport-safari

# Node.js / Electron(USB)
npm install @openloop/sdk-core @openloop/transport-usb

# Node.js / Electron(Bluetooth)
npm install @openloop/sdk-core @openloop/transport-ble

Bitcoin のアドレス導出・PSBT 組み立てなどの出口(@openloop/sdk-core/bitcoin)は、@openloop/sdk-core に含まれます。別のパッケージは要りません。

基本フロー

すべての接続方式で共通の 4 ステップです。

1. Transport 接続  →  2. App 作成  →  3. 操作  →  4. 切断
import { EthereumApp, DEFAULT_ETH_PATH } from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

// 1. Transport 接続(ユーザージェスチャー内で呼び出す)
const transport = await WebHidTransport.connect()

// 2. App 作成
const eth = new EthereumApp(transport)

// 3. 操作
const { address } = await eth.getAddress(DEFAULT_ETH_PATH)
console.log('Address:', address)

// 4. 切断
await transport.close()

注意: WebHidTransport.connect() と WebBleTransport.connect() はブラウザのセキュリティ制約により、ボタンクリック等のユーザージェスチャー内で呼び出す必要があります。

Ethereum の完全サンプル

import {
  EthereumApp,
  DEFAULT_ETH_PATH,
  type SignatureResult,
} from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

async function main() {
  // 接続
  const transport = await WebHidTransport.connect()
  const eth = new EthereumApp(transport)

  try {
    // アドレス取得
    const { address, publicKey } = await eth.getAddress(DEFAULT_ETH_PATH)
    console.log('Address:', address)
    console.log('Public Key:', publicKey)

    // メッセージ署名(EIP-191 personal_sign)
    const msgSig: SignatureResult = await eth.signPersonalMessage(
      DEFAULT_ETH_PATH,
      'Hello, Openloop!',
      1 // chainId: Ethereum Mainnet
    )
    console.log('Message Signature:', { v: msgSig.v, r: msgSig.r, s: msgSig.s })

    // トランザクション署名(RLP エンコード済みの raw tx を渡す)
    const rawTx = 'f86c...' // RLP-encoded transaction hex
    const txSig: SignatureResult = await eth.signTransaction(DEFAULT_ETH_PATH, rawTx)
    console.log('TX Signature:', { v: txSig.v, r: txSig.r, s: txSig.s })

    // EIP-712 Typed Data 署名
    const typedSig: SignatureResult = await eth.signTypedData(
      DEFAULT_ETH_PATH,
      'aabbccdd...', // domainSeparatorHash (32 bytes hex)
      '11223344...', // messageHash (32 bytes hex)
      1
    )
    console.log('TypedData Signature:', typedSig)
  } finally {
    await transport.close()
  }
}

Bitcoin の完全サンプル

import {
  BitcoinApp,
  BTC_MAINNET_PATH,
  type BtcMessageSignature,
} from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

async function main() {
  const transport = await WebHidTransport.connect()
  const btc = new BitcoinApp(transport)

  try {
    // アドレス取得(SegWit bech32)
    const { address, publicKey, chainCode } = await btc.getAddress(false) // mainnet
    console.log('Address:', address)  // bc1q...

    // メッセージ署名(BIP-137)
    const msgSig: BtcMessageSignature = await btc.signMessage(
      'Hello, Bitcoin!',
      BTC_MAINNET_PATH
    )
    console.log('Signature (base64):', msgSig.signature)

    // PSBT 署名
    const psbtHex = '70736274ff...' // PSBT binary as hex
    const signedPsbtHex = await btc.signPsbtHex(psbtHex)
    console.log('Signed PSBT:', signedPsbtHex)

    // PSBT 署名(入力パス指定)
    const signedWithPaths = await btc.signPsbtWithPaths(psbtHex, [
      { index: 0, path: "84'/0'/0'/0/0" },
      { index: 1, path: "84'/0'/0'/0/1" },
    ])
    console.log('Signed PSBT with paths:', signedWithPaths)
  } finally {
    await transport.close()
  }
}

Bitcoin の出口: ネットワークなしで動く最短の例

@openloop/sdk-core/bitcoin は、アドレスの導出と検証・路の組み立て・UTXO の選択・PSBT の組み立てなどを行う関数群です。モジュールの中に状態を持たず、網・端点・路・口座はすべて引数で渡します。デバイスとの通信(署名)は BitcoinApp が担います。

次の例は、口座の拡張公開鍵(xpub)から受け取りアドレスを導き、宛先を検証します。デバイスもネットワークも要りません。

import {
  decodeExtendedPubkey,
  deriveAddresses,
  validateAddress,
  type BtcXpub,
} from '@openloop/sdk-core/bitcoin'

// 口座の段(m/84'/0'/0')の xpub。デバイスから取った物を使う(下記)
const xpubString =
  'xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V'

const k = decodeExtendedPubkey(xpubString)
const xpub: BtcXpub = {
  publicKey: k.publicKey,
  chainCode: k.chainCode,
  parentFingerprint: k.parentFingerprint,
  depth: k.depth,
  xpub: xpubString,
}

// 受け取りの列(change = 0)の先頭から 2 本
const list = deriveAddresses(xpub, { script: 'p2wpkh', account: 0 }, 'mainnet', 0, 0, 2)
console.log(list[0].address) // bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
console.log(list[0].path)    // m/84'/0'/0'/0/0

// 宛先の検証(読めない/別の網のアドレスは BtcError を投げる)
console.log(validateAddress(list[0].address, 'mainnet')) // { kind: 'p2wpkh' }

この xpub は、公開されているテスト用のニーモニック(abandon … about)の BIP-84 口座のものです。

デバイスから口座の xpub を取るときは、BitcoinApp.getExtendedPubkey を使います。

import { BitcoinApp } from '@openloop/sdk-core'
import { accountPathOf, deriveAddresses } from '@openloop/sdk-core/bitcoin'

const btc = new BitcoinApp(transport)
const acct = { script: 'p2wpkh', account: 0 } as const

// 口座の段の路(m/84'/0'/0')の xpub を取る。BtcXpub がそのまま返る
const xpub = await btc.getExtendedPubkey(accountPathOf(acct, 'mainnet'))
const [first] = deriveAddresses(xpub, acct, 'mainnet', 0, 0, 1)
console.log(first.address, first.path)

script は 'p2pkh'(BIP-44)・'p2sh-p2wpkh'(BIP-49)・'p2wpkh'(BIP-84)、網は 'mainnet'・'testnet3'・'testnet4' です。誤りは BtcError(code で見分けます)で届きます(エラー処理)。残高の走査・UTXO の選択・PSBT の組み立て・放送の関数は API リファレンスの「Bitcoin の出口」、デバイスでの署名は Bitcoin API を参照してください。

Bluetooth 接続

WebHID を WebBLE に置き換えるだけで、他のコードは同じです。

import { EthereumApp, DEFAULT_ETH_PATH } from '@openloop/sdk-core'
import { WebBleTransport } from '@openloop/transport-webble'

// Bluetooth 接続(ユーザージェスチャー内で呼び出す)
const transport = await WebBleTransport.connect()

// 以降は WebHID と同じ
const eth = new EthereumApp(transport)
const { address } = await eth.getAddress(DEFAULT_ETH_PATH)

LocalWS 接続(全ブラウザ対応)

Openloop Connect デスクトップアプリ経由で接続します。Firefox や Safari でも動作します。

import { EthereumApp } from '@openloop/sdk-core'
import { LocalWsTransport } from '@openloop/transport-local'

// Connect アプリが起動しているか確認
const available = await LocalWsTransport.isAvailable()
if (!available) {
  console.log('Openloop Connect を起動してください')
}

// 接続
const transport = new LocalWsTransport()
await transport.open()

// 以降は同じ
const eth = new EthereumApp(transport)

React での利用

import { useState, useCallback } from 'react'
import { EthereumApp, DEFAULT_ETH_PATH, type ITransport } from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

function WalletButton() {
  const [address, setAddress] = useState<string>('')
  const [transport, setTransport] = useState<ITransport | null>(null)

  const connect = useCallback(async () => {
    const t = await WebHidTransport.connect()
    setTransport(t)
    const eth = new EthereumApp(t)
    const { address } = await eth.getAddress(DEFAULT_ETH_PATH)
    setAddress(address)
  }, [])

  const disconnect = useCallback(async () => {
    await transport?.close()
    setTransport(null)
    setAddress('')
  }, [transport])

  return (
    <div>
      {address ? (
        <>
          <p>{address}</p>
          <button onClick={disconnect}>Disconnect</button>
        </>
      ) : (
        <button onClick={connect}>Connect Wallet</button>
      )}
    </div>
  )
}

サンプルアプリの useOpenloop フック(packages/sample-app/src/hooks/useOpenloop.ts)は、全 Transport 切り替え・WalletConnect 統合・自動再接続を含む実装例です。

自動再接続

WebHID と WebBLE は、以前接続が許可されたデバイスへの自動再接続をサポートしています。

import { EthereumApp } from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

// 以前許可されたデバイスに自動再接続(ユーザージェスチャー不要)
const transport = await WebHidTransport.reconnect()
if (transport) {
  // 再接続成功
  const eth = new EthereumApp(transport)
} else {
  // デバイスが見つからない → connect() でペアリングが必要
}

次のステップ