Skip to content
HACKTEZ
<< a FAFOlab joint >>Bluesky @hacktez.comhack.tez is open source and unlicensed — public domain. copy it. use it. github.com/skullzarmy/hack-tez
skills

Beacon SDK (TZIP-10)

TZIP-10 compliant octez.connect SDK for Tezos wallet integration. Covers DAppClient, WalletClient, permission flows, message types, and transport layers.

beacon-sdk.md

Skill: octez.connect SDK — Tezos Wallet Interaction (tzip-10)

skill_type: hybrid
domain: Tezos blockchain / dApp & wallet development
version: 1.0

Overview: What is octez.connect and tzip-10

octez.connect is a TypeScript SDK that implements the tzip-10 wallet interaction standard for the Tezos blockchain. It enables decentralised applications (dApps) and wallets to communicate securely over multiple transport layers (P2P, browser extension, mobile deep-link) without a centralised relay.

PropertyValue
npm package@tezos-x/octez.connect-sdk
GitHubhttps://github.com/trilitech/octez.connect
Docshttps://octez-connect.tezos.com
LicenseMIT
LanguageTypeScript (95%), CSS (3.6%), JS (1.1%)
Maintained byTrilitech (fork of airgap-it/beacon-sdk)
Initial releaseFebruary 13, 2026

tzip-10 is the Tezos improvement proposal that standardises the handshake, pairing, and message-passing protocol between a dApp and a Tezos wallet. The SDK abstracts this protocol into two primary client classes:

  • DAppClient — used in frontend applications to request permissions and send operations to a connected wallet.
  • WalletClient — used inside wallet implementations to receive and respond to messages from dApps.

When to use this SDK directly vs. Octez.js + BeaconWallet: For simple permission requests and account reading, use DAppClient directly. For complex smart-contract interactions (calling entrypoints, estimating fees, batch operations), prefer Octez.js with its BeaconWallet class, which wraps this SDK with higher-level contract helpers.


Installation

npm i --save @tezos-x/octez.connect-sdk

TypeScript import:

import {
  DAppClient,
  WalletClient,
  BeaconEvent,
  BeaconMessageType,
  PermissionScope,
  Network,
  NetworkType,
} from '@tezos-x/octez.connect-sdk';

Prerequisites:

  • Node.js and npm installed
  • TypeScript project (the SDK is TypeScript-first; type definitions are bundled)
  • Basic familiarity with Tezos accounts, public keys, and network identifiers
  • async/await patterns in TypeScript/JavaScript

DApp Integration

1. Instantiate DAppClient

The minimum required option is name — this string is shown to the user in the wallet pairing UI.

const dAppClient = new DAppClient({ name: 'My Tezos dApp' });

Extended options (all optional beyond name):

const dAppClient = new DAppClient({
  name: 'My Tezos dApp',
  // Additional options available — see octez-connect.tezos.com for full API
});

2. Subscribe to Active Account Changes

Register an event listener before requesting permissions. The callback fires whenever the connected account changes (initial connection, wallet switch, disconnect).

dAppClient.subscribeToEvent(
  BeaconEvent.ACTIVE_ACCOUNT_SET,
  async (account) => {
    if (account) {
      console.log('Active account:', account.address);
    } else {
      console.log('Wallet disconnected');
    }
  }
);

3. Request Permissions

requestPermissions() is async and opens the wallet pairing/approval UI. On success it returns an object that includes at minimum an address field (the user's Tezos address).

try {
  const permissions = await dAppClient.requestPermissions();
  console.log('Connected address:', permissions.address);
} catch (error) {
  console.error('Permission request failed or was rejected:', error);
}

What to expect:

  • If no wallet is paired, the SDK displays a QR code or wallet-selector modal for pairing.
  • If a wallet is already paired, the request is forwarded directly and the wallet prompts the user to approve.
  • The resolved object contains address, publicKey, network, and granted scopes.

4. Full Minimal DApp Example

import { DAppClient, BeaconEvent } from '@tezos-x/octez.connect-sdk';

const client = new DAppClient({ name: 'My dApp' });

client.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, async (account) => {
  console.log('Account set:', account?.address ?? 'none');
});

document.getElementById('connectBtn')!.addEventListener('click', async () => {
  const permissions = await client.requestPermissions();
  console.log('Granted to address:', permissions.address);
});

Wallet Integration

1. Instantiate WalletClient

const walletClient = new WalletClient({ name: 'My Tezos Wallet' });

2. Initialise the Client

init() must be called before connect(). It establishes the P2P transport connection and prepares the client to receive incoming messages.

await walletClient.init();

3. Connect and Handle Incoming Messages

connect(messageHandler) takes an async callback that is invoked for every incoming BeaconMessage. A production wallet must handle all BeaconMessageType variants (see reference below).

await walletClient.connect(async (message) => {
  console.log('Received message type:', message.type);

  if (message.type === BeaconMessageType.PermissionRequest) {
    // Handle permission request — see workflow below
  }

  // Handle other message types (OperationRequest, SignPayloadRequest, etc.)
});

4. Respond to a PermissionRequest

When the wallet approves a permission request, call walletClient.respond() with a PermissionResponseInput object.

await walletClient.respond({
  type: BeaconMessageType.PermissionResponse,
  id: message.id,                   // MUST match incoming message.id exactly
  publicKey: '<tezos-public-key>',  // Real edpk… / sppk… / p2pk… key
  network: message.network,         // Echo back the requested network
  scopes: [PermissionScope.OPERATION_REQUEST], // Scopes the wallet grants
});

Important: The id field must match the id of the incoming message, or the dApp will ignore or reject the response.

5. Full Minimal Wallet Example

import {
  WalletClient,
  BeaconMessageType,
  PermissionScope,
} from '@tezos-x/octez.connect-sdk';

const wallet = new WalletClient({ name: 'My Wallet' });

await wallet.init();

await wallet.connect(async (message) => {
  if (message.type === BeaconMessageType.PermissionRequest) {
    console.log('dApp requests permissions on network:', message.network);

    // Prompt user in wallet UI, then respond:
    await wallet.respond({
      type: BeaconMessageType.PermissionResponse,
      id: message.id,
      publicKey: userPublicKey,       // Retrieve from keystore
      network: message.network,
      scopes: [PermissionScope.OPERATION_REQUEST],
    });
  }
});

BeaconEvent Reference

Subscribe to events on a DAppClient instance using subscribeToEvent(eventType, callback).

EventTriggerCallback payload
BeaconEvent.ACTIVE_ACCOUNT_SETAccount connected, switched, or clearedAccountInfo | undefined

The complete list of BeaconEvent values is documented at octez-connect.tezos.com. Common additional events include wallet pairing state changes, request success/failure notifications, and transport status updates.

Subscription pattern:

client.subscribeToEvent(BeaconEvent.<EVENT_NAME>, async (payload) => {
  // handle payload
});

BeaconMessageType Reference

BeaconMessageType is an enum used in both the type field of incoming messages and the type field of response objects. A production WalletClient must handle all relevant variants.

Enum valueDirectionDescription
BeaconMessageType.PermissionRequestdApp → WalletWallet should prompt user to grant access
BeaconMessageType.PermissionResponseWallet → dAppWallet grants or denies permission
BeaconMessageType.OperationRequestdApp → WalletdApp requests that the wallet broadcast an operation
BeaconMessageType.OperationResponseWallet → dAppWallet returns the operation hash
BeaconMessageType.SignPayloadRequestdApp → WalletdApp requests a payload signature
BeaconMessageType.SignPayloadResponseWallet → dAppWallet returns the signature
BeaconMessageType.BroadcastRequestdApp → WalletdApp requests raw broadcast of a pre-signed operation
BeaconMessageType.BroadcastResponseWallet → dAppWallet returns the operation hash
BeaconMessageType.ErrorWallet → dAppWallet reports an error back to the dApp
BeaconMessageType.DisconnectEither directionPeer signals intentional disconnection

Refer to octez-connect.tezos.com for the complete, authoritative enum surface; the table above covers the most common variants.


PermissionScope Reference

PermissionScope values are granted by the wallet in a PermissionResponse and declare what operations the dApp is allowed to request in subsequent messages.

ScopeMeaning
PermissionScope.OPERATION_REQUESTdApp may request the wallet to sign and broadcast operations
PermissionScope.SIGNdApp may request the wallet to sign arbitrary payloads

Grant only the scopes the dApp actually needs. Wallets should validate requested scopes and may grant a subset of what was requested.


PermissionResponseInput Schema

All fields are required when constructing a PermissionResponse.

FieldTypeDescription
typeBeaconMessageType.PermissionResponseMust be this exact enum value
idstringMust exactly match the id of the incoming PermissionRequest
publicKeystringThe user's Tezos public key (edpk…, sppk…, or p2pk…)
networkNetworkEcho the network object from the incoming request
scopesPermissionScope[]Array of scopes the wallet grants to the dApp

Network object shape:

{
  type: NetworkType.MAINNET | NetworkType.GHOSTNET | NetworkType.CUSTOM,
  name?: string,   // Optional human-readable name (for custom networks)
  rpcUrl?: string, // Optional RPC endpoint (for custom networks)
}

Common Workflows

Full Permission Handshake (dApp ↔ Wallet)

dApp                                    Wallet
 │                                          │
 │── requestPermissions() ────────────────► │
 │   (BeaconMessageType.PermissionRequest)  │
 │                                          │── Prompt user
 │                                          │
 │◄─────────────── respond() ──────────────│
 │   (BeaconMessageType.PermissionResponse) │
 │                                          │
 │  ACTIVE_ACCOUNT_SET event fires          │
 │  permissions.address available           │

Step-by-step:

  1. dApp instantiates DAppClient and subscribes to BeaconEvent.ACTIVE_ACCOUNT_SET.
  2. dApp calls dAppClient.requestPermissions().
  3. SDK opens pairing modal or forwards to paired wallet via transport.
  4. Wallet's connect() callback fires with a PermissionRequest message.
  5. Wallet prompts user to approve.
  6. Wallet calls walletClient.respond() with PermissionResponseInput.
  7. dApp's requestPermissions() promise resolves with permissions.address.
  8. ACTIVE_ACCOUNT_SET event fires on the dApp side.

Operation Request Flow

// dApp side — after permissions are granted
// Prefer Octez.js + BeaconWallet for this; shown here for completeness.

// Wallet side — in the connect() callback
if (message.type === BeaconMessageType.OperationRequest) {
  const operationHash = await signAndBroadcast(message.operationDetails);
  await wallet.respond({
    type: BeaconMessageType.OperationResponse,
    id: message.id,
    transactionHash: operationHash,
  });
}

Sending an Error Back to the dApp

await wallet.respond({
  type: BeaconMessageType.Error,
  id: message.id,
  errorType: BeaconErrorType.ABORTED_ERROR, // User cancelled
});

Transport Layers

The SDK supports three transport mechanisms. Transport selection is automatic based on what is available in the environment.

TransportUse caseNotes
P2P (Matrix)Cross-device pairing via QR codeDefault; works in all environments
Browser extensionSame-browser wallet extension (e.g., Temple)Requires files served over HTTP (http://), not file://
Mobile deep-link (iOS URL scheme)Same-device iOS wallet pairingiOS wallet must register a custom URL scheme

Browser Extension Requirement

⚠️ If testing locally with browser extension transport, you must serve your HTML files over a local web server. Opening file:// URLs will cause the extension transport to fail silently.

# Serve locally on port 8000
python3 -m http.server 8000
# Then open: http://localhost:8000/example-dapp.html

iOS Custom URL Scheme

iOS wallet apps that want to support same-device dApp connections must configure a custom URL scheme in their app's Info.plist. This scheme is registered in the beacon-wallet-list entry so dApps can deep-link directly to the wallet on the same device.


Higher-Level Abstraction: Octez.js + BeaconWallet

For complex Tezos smart-contract interactions, do not use DAppClient directly. Instead use Octez.js (the Tezos TypeScript library) with its BeaconWallet adapter, which wraps this SDK.

import { TezosToolkit } from '@taquito/taquito'; // or octez.js equivalent
import { BeaconWallet } from '@taquito/beacon-wallet'; // or octez.js equivalent

const Tezos = new TezosToolkit('https://ghostnet.ecadinfra.com');
const wallet = new BeaconWallet({ name: 'My dApp' });

Tezos.setWalletProvider(wallet);

// Request permissions
await wallet.requestPermissions({ network: { type: NetworkType.GHOSTNET } });

// Call a contract entrypoint
const contract = await Tezos.wallet.at('KT1...');
const op = await contract.methods.myEntrypoint(param).send();
await op.confirmation();

Decision guide:

NeedRecommended tool
Connect wallet, read addressDAppClient directly
Sign payloadsDAppClient directly
Call contract entrypointsOctez.js + BeaconWallet
Estimate fees, batch operationsOctez.js + BeaconWallet
Implement a wallet (receive messages)WalletClient directly

Adding a Wallet to the Ecosystem

To make a new wallet visible in the octez.connect pairing modal (so dApps using DAppClient can discover it):

  1. Fork the beacon-wallet-list repository (linked from the octez.connect GitHub org).
  2. Add a wallet entry JSON file with your wallet's metadata: name, logo, desktop/mobile availability, browser-extension IDs, and (for iOS) the custom URL scheme.
  3. Submit a pull request to the beacon-wallet-list repository.
  4. Once merged, the octez.connect SDK will include your wallet in its discovery list.

For iOS wallets: the urlScheme field in your wallet entry must match the custom URL scheme registered in your app's Info.plist.


Mobile SDKs

Native SDK alternatives exist for mobile wallet developers. These have different APIs from the TypeScript SDK — this document covers TypeScript only.

PlatformLanguageRepository
iOSSwiftAvailable under the Trilitech GitHub organisation
AndroidKotlinAvailable under the Trilitech GitHub organisation

Refer to each platform's README for platform-specific initialisation, pairing, and message-handling APIs.


Local Development and Testing

Build from Source

git clone https://github.com/trilitech/octez.connect.git
cd octez.connect
npm i
npm run build
npm run test

The repository uses a Lerna monorepo structure. Packages are located under /packages.

Run Example Files

The repo includes built example HTML files for browser testing:

  • example-dapp.html / dapp.html — test DApp behaviour
  • example-wallet.html / wallet.html — test Wallet behaviour
# Serve from the repo root
python3 -m http.server 8000

# Open in browser:
# http://localhost:8000/example-dapp.html   (dApp side)
# http://localhost:8000/example-wallet.html (Wallet side)

Open both URLs in separate browser tabs or windows to simulate the full pairing flow locally.


Edge Cases and Gotchas

1. id Mismatch in Responses

The id field in every response object must exactly match the id of the incoming request. If they differ, the dApp will not match the response to its pending promise and the request will hang indefinitely.

// ✅ Correct
await wallet.respond({ type: BeaconMessageType.PermissionResponse, id: message.id, ... });

// ❌ Wrong — using a new or hardcoded id
await wallet.respond({ type: BeaconMessageType.PermissionResponse, id: 'some-other-id', ... });

2. Public Key Format

The publicKey field in PermissionResponseInput must be a real, correctly-encoded Tezos public key — not a placeholder string. Accepted prefixes:

Key typePrefix
Ed25519edpk…
Secp256k1sppk…
P-256p2pk…

Passing a fake or malformed public key will cause address derivation to fail on the dApp side.

3. Scope Override Caution

The wallet example in the repository demonstrates overriding the dApp's requested scopes — granting only OPERATION_REQUEST regardless of what the dApp asked for. In a production wallet:

  • Validate that requested scopes are recognised and safe.
  • Present the user with exactly what is being granted.
  • Consider rejecting requests for scopes your wallet does not support.

4. Browser Extension Requires HTTP Transport

Opening example files via file:// disables browser-extension transport. Always serve files over http://localhost during development.

5. init() Before connect()

Calling walletClient.connect() without first calling walletClient.init() will fail. Always await walletClient.init() before await walletClient.connect(handler).

6. Handle All Message Types in Production

The connect() callback receives all BeaconMessageType variants. A wallet that only handles PermissionRequest and ignores the rest will leave dApp operation requests hanging. Implement a handler branch (or an error response) for every type the wallet receives.

7. Inherited "Beacon" Naming

Because the SDK is forked from airgap-it/beacon-sdk, many identifiers retain the Beacon prefix (e.g., BeaconEvent, BeaconMessageType, BeaconWallet). These are octez.connect SDK classes, not a separate "Beacon Protocol" — the naming is a legacy convention from the upstream fork.


Caveats & Limitations

  • Incomplete public API documentation in README: The full surface of BeaconEvent, BeaconMessageType, and PermissionScope is not exhaustively listed in the GitHub README. Always cross-reference with octez-connect.tezos.com for the authoritative API reference.
  • Early-stage project: The repository has a small number of stars and watchers as of the initial release (February 2026). Verify stability and release cadence before committing to production use.
  • No changelog or versioning in README: Pin your dependency to a specific semver range and monitor the GitHub releases page for breaking changes.
  • Mobile SDK APIs differ: iOS (Swift) and Android (Kotlin) SDKs have their own APIs; TypeScript patterns shown here do not apply directly.
  • Upstream fork divergence: Future changes in airgap-it/beacon-sdk may or may not be merged into this fork. The two projects may diverge over time.
  • BeaconWallet / Octez.js integration details: The exact import paths and API for BeaconWallet in Octez.js are governed by Octez.js documentation, not this SDK's README. Consult the Octez.js documentation separately.
download beacon-sdk.md