本文へ移動

外部NEARウォレット

Seamsのしきい値ウォレットを作成する代わりに、Meteor、HOT Wallet、Intear Walletを接続します。

外部NEARウォレット ​

アプリで次の2つの選択肢を提供できます。

  • Seamsウォレットを使う: 既存のSeams認証フローで、しきい値ウォレットを作成またはアンロックします。
  • 既存のウォレットを接続する: アプリが有効にしたウォレットから選び、そのウォレットでリクエストを承認します。

@seams/wallet@0.42.0は外部NEARウォレットの接続、取引送信、MeteorのsignMessage APIに対応しています。

ウォレットとネットワーク ​

ウォレットIDウォレット承認画面実装対象ネットワーク実ウォレットでの検証
meteorMeteor Walletブラウザー拡張機能メインネットとテストネットテストネットで接続、取引の最終実行、コントラクトの読み戻しを検証済み
hotHOT Walletブラウザー拡張機能メインネットのみメインネット接続とポップアップを閉じた後の再試行を検証済み。取引署名は未実施。
intearIntear Walletwallet.intear.techのポップアップメインネットとテストネット未実施

実装対象のネットワークでも、実ウォレットによる検証が完了しているとは限りません。 HOTのテストネット接続は検証対象になっていないため、セレクターでは無効になります。 Intearはポップアップを使用します。他の2つは拡張機能を使用します。

最初の導入対象はMeteorで、その次がHOTです。 HOTの取引署名と、Intearの実ウォレット検証は保留中です。

統合の境界 ​

外部コネクターはアプリのドキュメント内で動作します。 SeamsWebProvider、Seamsへのログイン、Wallet Session、ウォレットiframe、しきい値署名用バックエンドは不要です。 ブラウザー、利用可能な外部ウォレット、選択したネットワーク用の信頼できるNEAR RPC URLが必要です。 RPC URLとネットワークを一致させてください。

接続すると、アプリから外部アカウントを利用できるようになります。 この接続はConsoleユーザーの認証、Seamsによる鍵管理の開始、Seamsの署名ポリシーの承認にはなりません。 Seamsウォレットも提供する場合は、選択中のウォレットの種類を明示してください。 各操作には、そのウォレットに対応する署名APIを使用します。

署名鍵と承認画面は外部ウォレットが管理します。 コネクターは、オンチェーンのファンクションコールキーを要求しません。 Intearはポップアップへのリクエストを認証するため、メモリー内に一時鍵を作成します。 この鍵をオンチェーンの署名鍵として登録することはありません。

対応ウォレットを選ぶ ​

各ウォレットは明示的に有効化します。コントローラーのwalletsに指定してください。

  • ['meteor']:Meteorだけを有効にします。
  • ['meteor', 'hot']:MeteorとHOTをこの順序で有効にします。
  • ['meteor', 'hot', 'intear']:3つすべてを有効にします。
  • []:外部NEARウォレットをすべて無効にします。

セレクターには、指定したウォレットだけを表示します。 無効な選択肢にconnectを呼ぶと、アダプターを読み込まず、現在の接続も変更せずにunavailableを返します。 HOTは有効にしてもメインネット専用です。

実行中に変更する場合は、controller.setWallets(['meteor', 'hot'])を呼びます。 セレクターも自動で更新されます。接続済みまたは接続処理中のウォレットをリストから外すと、 ローカルで切断し、準備中の操作を無効にします。送信済み取引の取り消しや、外部ウォレット側の権限の取り消しは行いません。

コントローラーはリストをコピーし、重複するIDを除去して、指定順序を保持します。 元の配列を書き換えても設定は変わりません。変更にはsetWalletsを使用してください。

Reactの実装例 ​

まず組み込みのウォレットセレクターを使用します。 ウォレットの選択、接続、切断を処理します。レンダー間で同じコントローラーを保持してください。

tsx
import { useMemo } from 'react';
import { ExternalNearController } from '@seams/wallet/external-near';
import { ExternalNearWalletPicker } from '@seams/wallet/react/external-near';

function createController() {
  return new ExternalNearController({
    network: 'testnet',
    rpcUrl: 'https://rpc.testnet.near.org',
    wallets: ['meteor'],
  });
}

export function ConnectNearWallet() {
  const controller = useMemo(createController, []);

  return <ExternalNearWalletPicker controller={controller} />;
}

セレクターはアンマウント時に切断します。 独自のセレクターでは、useExternalNearでコントローラーを購読し、画面を閉じるときにdisconnect()を呼びます。 Seamsのプロバイダーは不要です。

取引を送信する ​

アプリの送信操作から、セレクターと同じコントローラーを渡して次の関数を呼びます。 残高のあるテストネットアカウントを使用し、送信先を自分のコントラクトに置き換えてください。 添付デポジットはゼロです。ネットワーク手数料はウォレットが支払います。

ts
import { functionCall } from '@seams/wallet';
import type { ExternalNearController } from '@seams/wallet/external-near';

export async function sendGreeting(controller: ExternalNearController) {
  const { connection } = controller.getSnapshot();
  if (connection.kind !== 'connected') return;

  const result = await controller.sendTransaction(connection, {
    receiverId: 'seams-v1.testnet',
    actions: [
      functionCall({
        method: 'set_greeting',
        args: { greeting: 'Hello from my wallet' },
      }),
    ],
  });

  if (!result.ok) {
    console.error(result.message);
    return;
  }

  const submission = result.value;
  const outcome = await controller.checkOutcome(submission);

  console.log(outcome.kind, submission.hash);
  return submission;
}

コンソールへの出力は、アプリの状態表示に置き換えてください。 結果がunresolvedの場合は返された送信情報を保持し、 controller.checkOutcome(submission)を再度呼んで状態を確認します。

この例は基本的な流れを示します。本番UIでは、リクエストの処理中に重複送信を無効にしてください。 送信状態が不明な場合は、次の取引を準備する前にウォレットの履歴を確認します。 結果と再試行のルールを参照してください。

所有確認メッセージに署名する ​

Meteorは、NEARのメッセージ署名規格NEP-413に対応しています。 アプリのチャレンジメッセージ、受信者、32バイトのnonceをそのまま渡します。 サーバーはnonceを発行し、使用済みのチャレンジを拒否してください。

ts
import type { ExternalNearController } from '@seams/wallet/external-near';

export async function signOwnershipChallenge(
  controller: ExternalNearController,
  challenge: { message: string; recipient: string; nonce: Uint8Array },
) {
  const connection = controller.getSnapshot().connection;
  if (connection.kind !== 'connected') throw new Error('Connect Meteor first.');
  const result = await controller.signMessage(connection, challenge);
  if (!result.ok) throw new Error(result.message);
  return result.value;
}

結果にはaccountId、publicKey、base64形式のsignatureが含まれます。 リクエストには任意のcallbackUrlとstateも指定できます。 コネクターは、返却前に署名とNEP-413ペイロードの一致を検証します。 取引を送信しないため、残高やネットワーク手数料は不要です。 アプリのサーバーは、指定ネットワークで鍵がアカウントに属することと、チャレンジの有効性を確認してください。 このコネクターでは、HOTとIntearのメッセージ署名はunavailableを返します。

コントローラーAPI ​

独自UIやReact以外のアプリでは、@seams/wallet/external-nearから ExternalNearControllerをインポートします。 React向けの機能は@seams/wallet/react/external-nearにあります。 @seams/wallet/reactとは別のインポート先です。

API動作
new ExternalNearController({ network, rpcUrl, wallets })ネットワーク、そのHTTP(S) RPC URL、有効なウォレットのリストを必ず指定します。不正な設定では例外が発生します。
connect(wallet)meteor、hot、intearへの接続を要求します。ExternalNearResult<ExternalNearConnection>を返します。
setWallets(wallets)有効なウォレットを更新し、削除した接続中のウォレットを切断します。
getSnapshot()wallets、connection、busyを取得します。接続状態はdisconnected、connecting、connectedのいずれかです。
subscribe(listener)スナップショットの変更を購読します。購読を解除する関数を返します。
sendTransaction(connection, input)現在の接続スナップショットを使用し、送金または関数呼び出しを送信します。ExternalNearResult<ExternalNearSubmission>を返します。
signMessage(connection, params)MeteorにNEP-413署名を要求し、検証後にExternalNearResult<ExternalNearSignedMessage>を返します。
checkOutcome(submission)保存したハッシュ、アカウント、ネットワークに基づきRPCで状態を確認します。confirmed、failed、unresolvedを返します。
disconnect()ローカルの権限状態を消去し、アダプターを破棄して、保留中の処理を無効にします。

アカウントは、ウォレットの接続確認画面で選択します。 接続済み状態にはwallet、accountId、network、generationがあります。 generationは接続の世代を表します。 署名にはスナップショット自体を渡してください。手動で構築したり、コピーしたりしないでください。 再接続や切断の後は、以前のスナップショットを利用できません。

送金と関数呼び出しには、既存のtransferやfunctionCallヘルパーを使用します。 現在の外部コネクターは、一括送信、委任アクション、ハードウェアウォレット、モバイル接続を公開していません。

結果と再試行のルール ​

sendTransactionが成功すると、送信ハッシュを返します。 これは最終実行の成功を保証しません。状態の確認が終わるまで送信結果を保持してください。

結果またはエラーコードアプリの対応
confirmed成功を表示します。RPCの取引識別情報が一致し、失敗したレシートのない最終実行を確認できています。
failedオンチェーンでの最終的な失敗を表示し、ハッシュを保持します。
unresolvedハッシュを保持し、checkOutcomeによる再確認を提供します。再送信しないでください。
unavailable新たな接続を試す前に、ウォレットの準備状態、ネットワーク対応、ポップアップ許可を確認します。
invalid_input承認を要求する前に、送信先、アクション、金額、ガスを修正します。
connection_changed新しいスナップショットを取得し、選択中のウォレットとアカウントをユーザーに再確認してもらいます。
pending_request現在のウォレットリクエストを完了またはキャンセルします。
submission_unknown次の取引を準備する前に、ウォレットとチェーンの履歴を確認します。

接続を拒否した場合、現在のコネクターはunavailableを返します。 取引リクエストの送出後は、拒否、通信の切断、不正な結果を、保守的にsubmission_unknownとして報告する場合があります。 ユーザーによる拒否だけを表す結果は公開していません。 承認画面を閉じても、取引が送信されなかったことを保証するものではありません。

signMessageは、拒否、通信の切断、不正な署名に対してunavailableを返します。 承認中や検証中に接続が変わった場合はconnection_changedを返します。 別のリクエストを処理中の場合はpending_requestを返します。

切断するとローカル状態を消去します。ウォレット側の既存のサイト権限は取り消しません。 ローカルで切断した後も、拡張機能の承認画面が残る場合があります。

トラブルシューティング ​

Meteor拡張機能は、ページの読み込み後にページ用APIを追加する場合があります。 初期化後に接続を再試行してください。以前の「未検出」表示が古いままの場合があります。 拡張機能をインストールしたブラウザープロファイルでアプリを開き、サイトへのアクセス許可を確認してください。

アプリのオリジンでIntearのポップアップを許可してください。 アダプターは、固定のウォレットオリジンと、そのリクエスト用に開いたポップアップからの応答だけを受け入れます。 別オリジンへの転送や、ネイティブアプリ向けのブリッジセッションは拒否します。

インポート先が見つからない場合は、@seams/walletのバージョン0.42.0がインストールされていることを確認してください。 運用ガイドの外部NEARウォレットのトラブルシューティングも参照してください。