# Aioha > All-In-One Hive Authentication ## Docs Aioha (All-In-One Hive Authentication) is an API that provides a common interface for working with different Hive login providers. This allows easier integration of Hive login and transacting on the network with fewer code. - [Introduction](/docs.md): Aioha (All-In-One Hive Authentication) is an API that provides a common interface for working with different Hive login providers. This allows easier integration of Hive login and transacting on the network with fewer code. ### Category Indexes - [Core API](/docs/category/core-api.md): The base library for Aioha. - [Magi](/docs/category/magi.md): Aioha-powered JavaScript/Typescript libraries for Magi network. ### Core API - [Custom Provider](/docs/core/custom-provider.md): Besides the built-in providers, a custom defined provider that extends AiohaBaseProvider class may be registered as Providers.Custom provider. This may be useful for backend use or development of another browser provider. - [Get Started](/docs/core/get-started.md): Aioha core may be installed through npm or imported through CDN using ES6 module import syntax. - [JSON-RPC Interface](/docs/core/jsonrpc.md): The JSON-RPC API for Hive wallet providers outlined in this page are implemented in Aioha. These are designed to be platform and transport agnostic. - [Usage](/docs/core/usage.md): Configure and use Aioha in 5 minutes or less. - [Wax Signer](/docs/core/wax.md): Aioha may be used to sign transactions on Wax Typescript library. ### FAQ 1. Why is plaintext private key login not supported? - [FAQ](/docs/faq.md): 1. Why is plaintext private key login not supported? ### Framework Libraries Framework-specific libraries that provide Aioha reactive states for ease of integration. - [Framework Libraries](/docs/framework.md): Framework-specific libraries that provide Aioha reactive states for ease of integration. - [Lit Provider](/docs/framework/lit.md): Lit provider for Aioha that uses Lit's context to provide reactive states in your Lit Element components. - [React Provider](/docs/framework/react.md): For React apps that are not using the ready-made UI. - [Svelte Provider](/docs/framework/svelte.md): Svelte provider for Aioha that uses Svelte's context API to provide reactive states in your Svelte components. - [Vue Provider](/docs/framework/vue.md): Vue provider for Aioha that uses Vue's provide/inject to provide reactive states in your Vue components. ### Magi - [Overview](/docs/magi/overview.md): Magi JS is JavaScript/TypeScript library for interacting with the Magi network with wallet interoperability features. - [Framework Providers](/docs/magi/providers.md): Reactive state providers for Magi JS, available for React, Vue, Svelte and Lit. These work similarly to the Aioha core framework providers but expose Magi-specific state. - [Usage](/docs/magi/usage.md): Configure and interact with Magi in 5 minutes or less. ### Media Branding - [Media Resources](/docs/media.md): Branding ### React UI - [React UI](/docs/react/ui.md): Opinionated React modal UI for Hive logins through Aioha, styled using Tailwind CSS. This provides a quick and easy way to bootstrap a React web app with ready to use Aioha-powered modal component. ### Ready Made UI Ready made modal containing all the UI components and logic required for Aioha. - [Ready Made UI](/docs/ui.md): Ready made modal containing all the UI components and logic required for Aioha. - [Localization (i18n)](/docs/ui/i18n.md): @aioha/react-ui and @aioha/lit-ui share a single canonical message catalog published as @aioha/i18n-keys. The catalog exposes namespaced message IDs, an English default, a framework-neutral Messages adapter, ICU MessageFormat support for pluralization and interpolation, a pseudo-locale for testing, RTL direction detection, and bundled translations for 16 languages. - [Lit UI](/docs/ui/lit.md): Opinionated Lit modal UI for Hive logins through Aioha, styled using Tailwind CSS. This provides a quick and easy way to bootstrap a Lit web app with ready to use Aioha-powered modal component. - [Web Components](/docs/ui/web-components.md): The ready made modal UI is available as a framework agnostic web component that can be integrated on vanilla HTML/JS or any web framework. --- # Full Documentation Content # Introduction Aioha (All-In-One Hive Authentication) is an API that provides a common interface for working with different Hive login providers. This allows easier integration of [Hive](https://hive.io) login and transacting on the network with fewer code. Aioha is designed for use in browser contexts and can be used through a variety of different ways: 1. [**Core API**](/docs/category/core-api.md) This is the base library containing the inner workings of Aioha. It provides the APIs needed to interact with different Hive login providers, all within one or two lines of code. Recommended when flexibility is important. 2. [**Framework Libraries**](#supported-frameworks) These are libraries that provide framework-specific conveniences for integrating Aioha. Optionally includes all the UI logic required for an Aioha-powered Hive login modal. Best for most developers. 3. [**Wax Signer**](/docs/core/wax.md) Aioha can be used as a transaction signer for applications powered by [Wax Typescript](https://gitlab.syncad.com/hive/wax/-/tree/develop/ts?ref_type=heads). ## Supported Providers * [Keychain](https://hive-keychain.com) * [HiveAuth](https://hiveauth.com) * [HiveSigner](https://hivesigner.com) * [Ledger](https://hiveledger.io) * [Peak Vault](https://vault.peakd.com) * [MetaMask Snap](https://snaps.metamask.io/snap/npm/hiveio/metamask-snap) ## Supported Frameworks * Universal Web Components ([Ready Made UI](/docs/ui/web-components.md)) * Lit ([Provider](/docs/framework/lit.md), [Ready Made UI](/docs/ui/lit.md)) * React ([Provider](/docs/framework/react.md), [Ready Made UI](/docs/react/ui.md)) * Vue ([Provider](/docs/framework/vue.md)) * Svelte ([Provider](/docs/framework/svelte.md)) ## Feature Matrix | Feature | Peak Vault | Keychain | HiveAuth | HiveSigner | Ledger | MetaMask | | --------------------------- | ---------- | -------- | -------- | ---------- | ------ | -------- | | Login | ✅ | ✅ | ✅ | ✅1 | ✅ | ✅7 | | Discover accounts | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | | Memo encryption | ✅ | ✅ | ❌4 | ❌ | ❌ | ✅ | | Memo decryption | ✅ | ✅ | ❌5 | ✅2 | ❌ | ✅ | | Sign message | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | Sign tx (without broadcast) | ✅ | ✅ | ❌3 | ❌ | ✅ | ✅ | | Sign tx (with broadcast) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Sign HF26 serialized tx | ❌ | ❌ | ❌ | ❌ | ❌6 | ✅ | | Transact using owner auth | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | 1 A HiveSigner access token will be returned instead of an ECDSA signature.
2 Memo must be encrypted using `@hivesigner`'s public posting key.
3 While technically supported by the provider, the PKSA generates the transaction headers instead of using supplied values. This is unhelpful for signing multisig transactions.
4 Developer claims to be possible however this is not documented anywhere therefore not implemented in Aioha.
5 Only available in protocol 1.0 that doesn't seem to be deployed therefore not implemented in Aioha.
6 Possible with blind signing, however this is not implemented in Aioha.
7 No signatures will be created, an empty result will be returned. ## Local Storage Reserved Keys Aioha uses certain keys in browser `localStorage` to store persistent logins, and in the case of HiveSigner provider, pass info from callback URL. > **Warning** > > Do **not** modify any values for the local storage keys below outside Aioha core and HiveSigner callback page. The following keys are reserved: * General: `aiohaUsername`, `aiohaProvider`, `aiohaPubKey`, `aiohaOtherLogins` * HiveAuth: `hiveauthToken`, `hiveauthKey`, `hiveauthExp` * HiveSigner: `hivesignerTxId`, `hivesignerToken`, `hivesignerExpiry`, `hivesignerUsername` * Ledger: `ledgerPath` * MetaMask: `mmHiveSnapAccIdx` --- ## [📄️ Get Started](/docs/core/get-started.md) [Aioha core may be installed through npm or imported through CDN using ES6 module import syntax.](/docs/core/get-started.md) --- ## [📄️ Overview](/docs/magi/overview.md) [Magi JS is JavaScript/TypeScript library for interacting with the Magi network with wallet interoperability features.](/docs/magi/overview.md) --- # Custom Provider Besides the [built-in providers](/docs.md#supported-providers), a custom defined provider that extends [`AiohaBaseProvider` class](https://github.com/aioha-hive/aioha/blob/main/src/providers/provider.ts) may be registered as `Providers.Custom` provider. This may be useful for backend use or development of another browser provider. ## Beekeeper Signer The recommended signer for backend usage is the [`BeekeeperProvider`](https://github.com/aioha-hive/aioha/blob/main/src/providers/custom/beekeeper.ts), which delegates key storage and signing to a [Beekeeper](https://gitlab.syncad.com/hive/beekeeper) wallet. Private keys never leave the wallet — the provider only holds public keys and passes digests to the wallet for signing. ### Installation ```sh pnpm i @aioha/aioha @hiveio/beekeeper ``` ### Usage ```js import { Aioha, AiohaClient, Providers, KeyTypes } from '@aioha/aioha' import { BeekeeperProvider } from '@aioha/aioha/build/providers/custom/beekeeper.js' import { SimpleEventEmitter } from '@aioha/aioha/build/lib/event-emitter.js' import createBeekeeper from '@hiveio/beekeeper' // Initialize a beekeeper wallet and import the private key const bk = await createBeekeeper({ inMemory: true }) const session = bk.createSession('some-salt') const { wallet } = await session.createWallet('my-wallet', 'password', true) const pubKey = await wallet.importKey('5somethingsomething') // Register the beekeeper provider with Aioha const aioha = new Aioha() aioha.registerCustomProvider( new BeekeeperProvider( wallet, { [KeyTypes.Posting]: pubKey, [KeyTypes.Active]: pubKey }, new AiohaClient(), new SimpleEventEmitter() ) ) // Login as usual const login = await aioha.login(Providers.Custom, 'your-username', { msg: 'Hello World' }) // Claim rewards const claim = await aioha.claimRewards() console.log(claim) ``` The `keys` map associates a [`KeyTypes`](https://github.com/aioha-hive/aioha/blob/main/src/types.ts) entry (`Posting`, `Active`, `Owner`, `Memo`) with the public key of the corresponding private key held in the wallet. Only key types present in the map can sign — attempting to use a missing key type throws. > **Custom sha-256 implementation** > > The constructor accepts an optional fifth argument to supply your own SHA-256 function (e.g. `sha256` from [`@noble/hashes`](https://github.com/paulmillr/noble-hashes)). If omitted, the browser-compatible default from Aioha is used. > **Persistent wallets** > > Use `inMemory: true` for ephemeral sessions only. For persistent storage, omit the flag and supply `storageRoot` — see the [Beekeeper docs](https://www.npmjs.com/package/@hiveio/beekeeper) for configuration. ## Plaintext Private Key > **Deprecated** > > `PlaintextKeyProvider` is deprecated in favor of the [Beekeeper signer](#beekeeper-signer). It holds the WIF in memory and should not be used in new code. ```js import { Aioha, Providers } from '@aioha/aioha' import { PlaintextKeyProvider } from '@aioha/aioha/build/providers/custom/plaintext.js' const aioha = new Aioha() aioha.registerCustomProvider(new PlaintextKeyProvider('5somethingsomething')) const login = await aioha.login(Providers.Custom, 'your-username', { msg: 'Hello World' }) ``` --- # Get Started Aioha core may be installed through npm or imported through CDN using ES6 module import syntax. ## NPM Installation **npm:** ```sh npm i @aioha/aioha ``` **yarn:** ```sh yarn add @aioha/aioha ``` **pnpm:** ```sh pnpm i @aioha/aioha ``` ## CDN Import ```html ``` ## Usage Example Instantiating Aioha, logging in and transacting on Hive can be done with just one or two lines of code for each task. Most configuration are provider-specific. ```js import { initAioha, Asset, KeyTypes, Providers } from '@aioha/aioha' // Instantiation const aioha = initAioha({ hiveauth: { name: 'Aioha', description: 'Aioha test app' }, hivesigner: { app: 'ipfsuploader.app', callbackURL: window.location.origin + '/hivesigner.html', scope: ['login', 'vote'] } }) // Get registered providers console.log(aioha.getProviders()) // Get current logged in user and provider name if (aioha.isLoggedIn()) { console.log(aioha.getCurrentUser(), aioha.getCurrentProvider()) } // Login with provider const login = await aioha.login(Providers.Keychain, 'hiveusername', { msg: 'Hello World', keyType: KeyTypes.Posting }) // Transfer 1 HIVE using logged in provider const xfer = await aioha.transfer('recipient', 1, Asset.HIVE, 'Transferred using Aioha with memo') // Vote comment with 100% weight const vote = await aioha.vote('author', 'permlink', 10000) // Claim rewards const rewardClaim = await aioha.claimRewards() ``` ## HiveSigner Callback Page HiveSigner provider requires a callback page that stores the login and transaction results into `localStorage` which is then used as callback data for the application. The function is included in [`src/lib/hivesigner-cb.ts`](https://github.com/aioha-hive/aioha/blob/main/src/lib/hivesigner-cb.ts) file in Aioha core and may be used as such: **CDN:** ```html ``` **NPM Module:** ```js import { hivesignerCb } from '@aioha/aioha/build/lib/hivesigner-cb' hivesignerCb() ``` ## Environment defines Aioha reads `process.env.NODE_DEBUG` from a transitive dependency. Define it as `false` in your bundler so the reference does not leak into the output. **Webpack:** ```js // webpack.config.cjs const { DefinePlugin } = require('webpack') module.exports = { // ... resolve: { // ... fallback: { url: false } }, plugins: [ // ... new DefinePlugin({ 'process.env.NODE_DEBUG': false }) ] } ``` **Vite:** ```ts // vite.config.ts import { defineConfig } from 'vite' export default defineConfig({ // ... define: { 'process.env.NODE_DEBUG': false } }) ``` ## Chunking To keep the main bundle as lightweight as possible, enable chunking (or code splitting) in your bundler if not already by default. **Webpack:** ```js // webpack.config.cjs module.exports = { // ... optimization: { splitChunks: { name: (module, chunks, cacheGroupKey) => { const allChunksNames = chunks.map((chunk) => chunk.name).join('-') return allChunksNames } } } } ``` --- # JSON-RPC Interface The JSON-RPC API for Hive wallet providers outlined in this page are implemented in Aioha. These are designed to be platform and transport agnostic. Any RPC methods not defined here shall be redirected to a Hive API node. ## Aioha RPC Errors For every RPC method, the error returned (if any) shall follow the convention below: ```js { "message": "human readable error message", "code": 4001 } ``` The error code returned are as follows, for Aioha-specific errors: > **Info** > > The application may use the error codes to provide a localized message to be displayed to the user. | Error Code | Name | Description | | ---------- | ----------------------- | ------------------------------------------------------------------------ | | 4001 | User Rejected Request | The user rejected the request. | | 4002 | Request Expired | The request has expired. | | 4003 | Network Error | A network error occured during the operation. | | 4100 | Unauthorized | The requested method and/or account has not been authorized by the user. | | 4200 | Unsupported Method | The requested method is not supported. | | 4201 | Unsupported Provider | The requested provider is not supported or registered. | | 4900 | Disconnected | The provider is disconnected. | | 4901 | Already connected | Account connection has already been established with the provider. | | 5000 | Unknown Error | Unknown error occurred while serving the request. | | 5001 | Extension Not Found | The required browser extension is not installed. | | 5002 | Missing Username | Username is required. | | 5003 | Missing/Invalid Options | Valid options are required. | | 5004 | Invalid Memo | Memo must start with a #. | | 5005 | Unsupported Key Type | Chosen key type is not supported for the requested operation. | | 5006 | Invalid Amount | Amount must be greater than or equal to 0. | | 5007 | Empty transaction | There are no operations in the transaction. | | 5200 | Nothing To Do | There are no actions required to complete the request. | | 5201 | Invalid Account Auth | Could not add/remove itself to/from account auth. | | 5900 | Connection Error | Failed to establish connection to the device. | | 5901 | Unassociated User | Username is not associated with the device. | | 5902 | Discovery Error | Failed to search accounts associated with the device. | | 5903 | Signature Request Error | Failed to obtain message/transaction signature from the device. | | 5904 | Hash signing disabled | Hash signing is not enabled on the device. | ## Usage ```js import { initAioha, Providers } from '@aioha/aioha' import { AiohaJsonRpc } from '@aioha/aioha/build/extras/jsonrpc-request.js' const aioha = initAioha() const rpc = new AiohaJsonRpc(aioha) // Login as usual const login = await aioha.login(Providers.Custom, 'your-username', { msg: 'Hello World' }) // Make an RPC request // This is shown for reference, ideally Wax should be used for Hive RPC client const result = await rpc.request({ method: 'database_api.get_dynamic_global_properties', params: {} }) // Listen to an event rpc.on('connect', () => console.log('connected')) // or directly through Aioha instance aioha.on('connect', () => console.log('connected')) ``` ## Aioha RPC Methods ### Decrypt Memo **Request:** ```js { "method": "aioha_api.decrypt_memo", "params": { "message": "#9RdpW9b7nfmdzipLJSZVQpcnpR3ipygGZg9DyxQZgBkbztb3NXBhSVKZJRLMs42KdcwUfHTkCWHpiGxTzR3jFfuaoY274chw3DyR16R2Esn5dMtw2Sa2Eh2W8EDochfiBKFmxAGauZRZChWz9Z5AmkS", "key_type": "posting" } } ``` **Result:** ```string // string #Hello World ``` ### Sign Message **Request:** ```js { "method": "aioha_api.sign_message", "params": { "message": "Hello World", "key_type": "posting" } } ``` **Result:** ```js // object { "signature": "2020647841097bca7d9e84c39df209ac2df9b42c5b194ee2a690304ce8c140690f5296566b5da0ae9d9950243871bf17fa26b34b671dc8284bf7fba767958a5508", "public_key": "STM5jruaymFQ93jwUp15r9SF5wPeaNvRKFE4nbbJv4vfHCk5a6zRu" } ``` ### Sign Transaction **Request:** ```js { "method": "aioha_api.sign_tx", "params": { "tx": { "ref_block_num": 27912, "ref_block_prefix": 1175138206, "expiration": "2024-06-07T10:23:57", "operations": [ [ "transfer", { "from": "randomvlogs", "to": "randomvlogs", "amount": "0.001 HIVE", "memo": "Aioha Test" } ] ], "extensions": [] }, "key_type": "posting" } } ``` **Result:** ```js // object { "ref_block_num": 27912, "ref_block_prefix": 1175138206, "expiration": "2024-06-07T10:23:57", "operations": [ [ "transfer", { "from": "randomvlogs", "to": "randomvlogs", "amount": "0.001 HIVE", "memo": "Aioha Test" } ] ], "extensions": [], "signatures": [ "20469faba4c128ef41c4e444ef5422e88ed9397d43a7c9713a8e59c170de45eea539b63ced8f4ff048442bdcca3b96848f6f740e655c971f9cae00b54535c8eea0" ] } ``` ### Sign and Broadcast Transaction **Request:** ```js { "method": "aioha_api.sign_and_broadcast_tx", "params": { "ops": [ ["vote", { "author": "sagarkothari88", "permlink": "2024-06-05-daily-updates-from-sagarkothari88", "voter": "techcoderx", "weight": 10000 }] ], "key_type": "posting" } } ``` **Result:** ```string // string 87c10767531071732fe15e6c34cf275900c342a3 ``` ### Get Registered Providers **Request:** ```js { "method": "aioha_api.get_registered_providers", "params": {} } ``` **Result:** ```js // array ["keychain", "peakvault", "ledger", "hiveauth", "hivesigner"] ``` ### Get Current Provider **Request:** ```js { "method": "aioha_api.get_current_provider", "params": {} } ``` **Result:** ```string // string hiveauth ``` ### Get Current User **Request:** ```js { "method": "aioha_api.get_current_user", "params": {} } ``` **Result:** ```string // string aioha ``` ### Is Logged In **Request:** ```js { "method": "aioha_api.is_logged_in", "params": {} } ``` **Result:** ```bool // boolean true ``` ### Is Provider Registered **Request:** ```js { "method": "aioha_api.is_provider_registered", "params": { "enabled": true } } ``` **Result:** ```bool // boolean true ``` ## Events Aioha provides several methods for listening to events: * `aioha.on(eventName, callback)`: Register a callback to listen for an event. The callback will be called every time the event occurs until explicitly unregistered. * `aioha.once(eventName, callback)`: Register a callback to listen for an event only once. The callback will be automatically unregistered after being called. * `aioha.off(eventName, callback)`: Unregister a specific callback for an event. If no callback is provided, all callbacks for the event will be unregistered. ### Connected The `connect` event is emitted every time an account has been successfully authenticated, resulting in a transition from a **disconnected** state to a **connected** state. ```js aioha.on('connect', () => void) ``` ### Disconnected The `disconnect` event is emitted every time an account was logged out and no accounts remain active. ```js aioha.on('disconnect', () => void) ``` ### Account Changed The `account_changed` event is emitted every time the current logged in accounts changes, under the following circumstances: 1. Another account has been added on top of the existing connected account 2. The connected account has been changed from one previously authenticated account to another 3. An account has been disconnected while leaving another account remain connected. 4. A connected but inactive account has been removed. ```js aioha.on('account_changed', () => void) ``` ### Login Request The `login_request` event is emitted every time a login request has been made to the user's wallet (excluding HiveAuth, see [below](#hiveauth-login-request)). ```js aioha.on('login_request', () => void) ``` ### Memo Request The `memo_request` event is emitted every time a memo encryption or decryption request has been made to the user's wallet. ```js aioha.on('memo_request', () => void) ``` ### Message Signature Request The `sign_msg_request` event is emitted every time a message signature request has been made to the user's wallet. ```js aioha.on('sign_msg_request', () => void) ``` ### Transaction Signature Request The `sign_tx_request` event is emitted every time a transaction signature request has been made to the user's wallet. ```js aioha.on('sign_tx_request', () => void) ``` ### HiveAuth Login Request The `hiveauth_login_request` event is emitted every time a new HiveAuth login request is created. This event contains the payload needed to display a QR code to be scanned using a HiveAuth compatible PKSA. Login request may be cancelled before timeout by calling `cancel()`. ```js aioha.on('hiveauth_login_request', (payload: string, evt: MessageType, cancel: () => void) => void) ``` ### HiveAuth Challenge Request The `hiveauth_challenge_request` event is emitted every time a new HiveAuth message signature request is created. Challenge request may be cancelled before timeout by calling `cancel()`. ```js aioha.on('hiveauth_challenge_request', (payload: string, evt: MessageType, cancel: () => void) => void) ``` ### HiveAuth Sign Transaction Request The `hiveauth_sign_request` event is emitted every time a new HiveAuth transaction signature request is created. Signature request may be cancelled before timeout by calling `cancel()`. ```js aioha.on('hiveauth_sign_request', (payload: string, evt: MessageType, cancel: () => void) => void) ``` --- # Usage Configure and use Aioha in 5 minutes or less. ## Instantiation Create an instance of Aioha. All providers are registered by default excluding HiveSigner, which is only registered when HiveSigner config is specified. This step also loads any persistent logins stored in `localStorage` (if available). Returns an `Aioha` object. Provider-specific configs are **optional** with exception of HiveSigner which is required to register HiveSigner provider. ```js import { initAioha, Asset, KeyTypes, Providers, VscStakeType } from '@aioha/aioha' const aioha = initAioha({ hiveauth: { name: 'Aioha', description: 'Aioha test app' }, hivesigner: { app: 'ipfsuploader.app', callbackURL: window.location.origin + '/hivesigner.html', scope: ['login', 'vote'] } }) ``` ### Configuration **HiveAuth:** * `name`: Short name of the app. Default: `Aioha Generic App` * `description` *(optional)*: App description * `icon` *(optional)*: URL to app icon **HiveSigner:** Refer to the [official documentation](https://www.npmjs.com/package/hivesigner#init-client) for complete details. * `app`: HiveSigner app account username. Setup the app account by following the instructions [here](https://docs.hivesigner.com/h/guides/get-started/integrate-hivesigner). * `callbackURL`: Callback URL. An example HTML page may be found [here](https://github.com/aioha-hive/aioha/blob/main/snippets/hivesigner.html). * `scope`: Authorized operations HiveSigner may perform on behalf of the user using the access token. A HiveSigner popup window will be shown when performing any operation outside the specified scope. ## Enums **Providers:** The `Providers` enum exported are as follows: ```js export enum Providers { Keychain = 'keychain', HiveSigner = 'hivesigner', HiveAuth = 'hiveauth', Ledger = 'ledger', PeakVault = 'peakvault', MetaMaskSnap = 'metamasksnap', ViewOnly = 'viewonly', Custom = 'custom' } ``` **KeyTypes:** The `KeyTypes` enum exported are as follows: ```js export enum KeyTypes { Posting = 'posting', Active = 'active', Owner = 'owner', Memo = 'memo' } ``` **Asset:** The `Asset` enum exported are as follows: ```js export enum Asset { HIVE = 'HIVE', HBD = 'HBD' } ``` ## Login > **Info** > > Also known as **Connect Wallet**. Login with specified provider and username. A signature request will be made to sign message `msg` using `keyType` key that is associated with the Hive account. **Usage:** ```js const login = await aioha.login(Providers.Keychain, 'hiveusername', { msg: 'Hello World', keyType: KeyTypes.Posting, loginTitle: 'Login', paths: ["m/48'/13'/1'/0'/0'"], hiveauth: { cbWait: (payload, evt, cancel) => { // WARNING: will be removed in Aioha v2 } }, metamask: { accountIdx: 0 validateUser: false } }) ``` * `msg`: Message to be signed * `keyType`: Key type to be used to sign the message * `loginTitle` *(optional)*: Title to be displayed on certain providers * `paths` *(optional)*: List of [SLIP-48](https://github.com/satoshilabs/slips/blob/master/slip-0048.md) paths to be used for wallets that support it to skip account search. Usually passed in from [account discovery](#discover-accounts) results. * `hiveauth.cbWait` *(optional)*: Callback function returning auth payload. It shall display a QR code using `payload` as data to be scanned by a HiveAuth PKSA. Login request may be cancelled before timeout by calling `cancel()`. * `metamask.accountIdx` *(optional)*: Account index to use for the MetaMask Snap connection. Defaults to 0. * `metamask.validateUser` *(optional)*: Whether to validate that the Hive account is associated with the public key derived by the MetaMask Snap. Defaults to false. > **Warning** > > The `hiveauth.cbWait` callback function is deprecated and will be removed in Aioha v2. Please use the [`hiveauth_login_request` event](/docs/core/jsonrpc.md#hiveauth-login-request) instead. **Result:** ```js { "provider": "keychain", "success": true, "result": "2020647841097bca7d9e84c39df209ac2df9b42c5b194ee2a690304ce8c140690f5296566b5da0ae9d9950243871bf17fa26b34b671dc8284bf7fba767958a5508", "publicKey": "STM5jruaymFQ93jwUp15r9SF5wPeaNvRKFE4nbbJv4vfHCk5a6zRu", "username": "randomvlogs" } ``` * `provider`: Name of provider selected by the user. See [Providers Enum](#enums) above for possibilities. * `success`: Boolean of whether login is successful or not * `error`: Error message, if any * `result`: ECDSA signature (or HiveSigner access token for HiveSigner provider) * `publicKey`: Public key used to sign the message (*undefined* for HiveSigner provider) * `username`: Logged in username ## Login and Decrypt Memo > **Not recommended** > > Login with signatures [above](#login) are preferred than with memo decryption due to limited support of memo cryptography [in certain providers](/docs.md#feature-matrix). Login with specified provider and username by decrypting a memo. **Usage:** ```js const login = await aioha.loginAndDecryptMemo(Providers.Keychain, 'hiveusername', { msg: 'Hello World', keyType: KeyTypes.Posting }) ``` * `msg`: Memo to be decrypted * `keyType`: Key type to be used to decrypt the message **Result:** ```js { "provider": "keychain", "success": true, "result": "#Login", "username": "randomvlogs" } ``` * `provider`: Name of provider selected by the user. See [Providers Enum](#enums) above for possibilities. * `success`: Boolean of whether the memo decryption is successful or not * `error`: Error message, if any * `result`: Decrypted message * `username`: Logged in username ## Login Non-Interactively May be used when authentication info (i.e. access token) is already available. This is especially useful for one-click login. > **Note** > > Only available in **HiveSigner**. ```js const loginResult = aioha.loginNonInteractive(Providers.HiveSigner, url.searchParams.get('username')!, { ignorePersistence: false, // optional, set to true to overwrite persistent logins already stored hivesigner: { accessToken: 'hivesigner_access_token_here', expiry: 123456 // timestamp in seconds } }) ``` ## Logout > **Info** > > Also known as **Disconnect Wallet**. **Current User:** Logout the current authenticated user. ```js await aioha.logout() ``` **All Users:** Logout the all authenticated users. ```js await aioha.logoutAll() ``` ## Switch User Switch the active user to another authenticated user listed [here](#list-other-users). Returns a boolean of whether the operation is successful. If failed, the user will be removed from the list and have to be reauthenticated. ```js aioha.switchUser('user2') ``` ## Remove User Remove a user listed [here](#list-other-users). Returns the authentication details of the user removed. Throws an error when the user does not exist. ```js aioha.removeOtherLogin('user2') ``` ## Discover Accounts Search the wallet provider for available accounts. Returns a map of username to account details which are dependent on the selected provider. Options include specifying roles to search accounts. Optionally a callback function may be provided to stream discovered accounts. The stop function may be called to halt account discovery and return all accounts discovered so far. **Usage:** ```js const discovered = await aioha.discoverAccounts(Providers.Ledger, (stream, stop) => void, { roles: [KeyTypes.Active, KeyTypes.Posting] }) ``` **Result:** ```json { "success": true, "result": { "techcoder": [ { "pubkey": "STM6dDqQ4LtL6NRvomWRivRC7fz3VJVvSMWukPVw5Rq4kTi7Bv2dH", "path": "m/48'/13'/0'/0'/0'", "role": "owner" }, { "pubkey": "STM8jWXpM41mZoVRMChR4b6cfZT7c6B1ZSky2URKHAmPrBLAB58MX", "path": "m/48'/13'/1'/0'/0'", "role": "active" }, { "pubkey": "STM6b69oq8QeQU68CYiPYDskvoVnZzL2kyia9L8TiccGU9K7DBZjD", "path": "m/48'/13'/4'/0'/0'", "role": "posting" } ], "vibecoderx": [ { "pubkey": "STM6oKFjYRcXfWMfvyguvnYPMYAWSXFgYGVgBymcer5EaZWE8dZEv", "path": "m/48'/13'/0'/1'/0'", "role": "owner" }, { "pubkey": "STM7gCJrBZhuiTQ1kwrAC3M17nxeNkK6qAipbZxWtvQbbso8Y6Gs7", "path": "m/48'/13'/1'/1'/0'", "role": "active" }, { "pubkey": "STM71VQTAciufuUYHpyCYqCXxPpp92KHDdAMzD7BdrWJQNPJD1i8v", "path": "m/48'/13'/4'/1'/0'", "role": "posting" } ] } } ``` **Stream:** ```json { "username": "techcoder", "pubkey": "STM6b69oq8QeQU68CYiPYDskvoVnZzL2kyia9L8TiccGU9K7DBZjD", "path": "m/48'/13'/4'/0'/0'", "role": "posting" } ``` ## View Only Mode View only mode allows logging in with any username to observe any account without signing capabilities. To enable, register the view only provider: ```js aioha.registerViewOnly() ``` Then login as usual: ```js const login = await aioha.login(Providers.ViewOnly, 'alice', {}) ``` ## Encrypt Memo Encrypt a message with the Hive public key of the recipient and the private key of the sender. **Encrypt:** ```js const encrypted = await aioha.encryptMemo('#secret message', KeyTypes.Posting, 'bob') ``` **Encrypt With Keys:** ```js const encrypted = await aioha.encryptMemoWithKeys('#secret message', KeyTypes.Posting, ['STM7KP2BCau71s1ihTqBtGiF4VeVWnJevxreNceDyisxGdt1baS4B', 'STM5jruaymFQ93jwUp15r9SF5wPeaNvRKFE4nbbJv4vfHCk5a6zRu']) ``` **Result:** ```json { "success": true, "result": "#Epz5g29x2Wqt5upn7UkuC4tFrR2zzDMaNFFt1KHFBkTAXnTLcK2jBRCX7FmM3bm3YV2FAShXVXkMY2idqRaDJ2KSLyGPJmYyXoY1JbEoKCLsncRssTxcmLkFiMSiyLeoa" } ``` **Result With Keys:** ```json { "success": true, "result": { "STM7KP2BCau71s1ihTqBtGiF4VeVWnJevxreNceDyisxGdt1baS4B": "#DTq11bwS4Wo5GmYifD4KCrRWpFtr5en4i57cXjZk5rEHfKqQTFEzxS3rYTgCtcdTi83HUuG51gNX9abLQVjAjwJPRAiXkDYqHJ17h3ytsYZ6SyuxjM7YCGmi3BFTg3Bx9", "STM5jruaymFQ93jwUp15r9SF5wPeaNvRKFE4nbbJv4vfHCk5a6zRu": "#DTq11bwS4Wo5GmYifD4KCrRWpFtr5en4i57cXjZk5rEHfH2s9YvNHSdcguDyKPs5waCx7RWBS2Qjb5uJ1zeBZSYyWpPW35XrecyGg6hrtjokByZYftCDuzjiNKi42SUr5" } } ``` ## Decrypt Memo Decrypt a Hive memo or message. **Usage:** ```js const decrypted = await aioha.decryptMemo('#encryptedmessage', KeyTypes.Posting) ``` Required arguments include the encrypted message starting with `#` and the `KeyTypes` enum for the key type to be used. **Result:** ```js { "success": true, "result": "#Hello World" } ``` * `success`: Boolean of whether memo decryption is successful or not * `error`: Error message, if any * `result`: Decrypted message ## Sign Message Retrieve an ECDSA signature for a message. **Usage:** ```js const signed = await aioha.signMessage('Message to sign', KeyTypes.Posting) ``` Required arguments include the message to sign and the `KeyTypes` enum for the key type to be used. **Result:** ```js { "success": true, "result": "205a8bf065ada352d309c546012b17b4e88f19fdeb704d9a26abe46f1c2f9bce3f4a4b05ffc4777e5005fc5c4b37cd7412a934b7170654df7761310e45cbf6483c", "publicKey": "STM5jruaymFQ93jwUp15r9SF5wPeaNvRKFE4nbbJv4vfHCk5a6zRu" } ``` * `success`: Boolean of whether message has been signed successfully or not * `error`: Error message, if any * `result`: ECDSA signature * `publicKey`: Public key used to sign the message ## Sign Transaction Sign a Hive transaction without broadcasting it. **Usage:** ```js const signed = await aioha.signTx({ "ref_block_num": 27912, "ref_block_prefix": 1175138206, "expiration": "2024-06-07T10:23:57", "operations": [ [ "transfer", { "from": "randomvlogs", "to": "randomvlogs", "amount": "0.001 HIVE", "memo": "Aioha Test" } ] ], "extensions": [] }, KeyTypes.Active) ``` Required arguments include the message to sign and the `KeyTypes` enum for the key type to be used (must not be `KeyTypes.Memo`). > **Important** > > Due to strict authority checks in HF28, the appropriate `KeyTypes` must be specified for the transaction so that the signature will be created using the correct key. For example, `KeyTypes.Active` must be specified for a transaction containing a transfer operation. **Result:** ```js { "success": true, "result": { "ref_block_num": 27912, "ref_block_prefix": 1175138206, "expiration": "2024-06-07T10:23:57", "operations": [ [ "transfer", { "from": "randomvlogs", "to": "randomvlogs", "amount": "0.001 HIVE", "memo": "Aioha Test" } ] ], "extensions": [], "signatures": [ "20469faba4c128ef41c4e444ef5422e88ed9397d43a7c9713a8e59c170de45eea539b63ced8f4ff048442bdcca3b96848f6f740e655c971f9cae00b54535c8eea0" ] } } ``` * `success`: Boolean of whether transaction has been signed successfully or not * `error`: Error message, if any * `result`: Full signed transaction ## Sign and Broadcast Transaction Sign and broadcast a Hive transaction. **Usage:** ```js const result = aioha.signAndBroadcastTx([ ['vote', { author: 'sagarkothari88', permlink: '2024-06-05-daily-updates-from-sagarkothari88', voter: 'techcoderx', weight: 10000 }] ], KeyTypes.Posting) ``` Required arguments include an array of operations for the transaction and the `KeyTypes` enum for the key type to be used (must not be `KeyTypes.Memo`). > **Important** > > Due to strict authority checks in HF28, the appropriate `KeyTypes` must be specified for the transaction so that the signature will be created using the correct key. For example, `KeyTypes.Posting` must be specified for a transaction containing a vote operation. **Result:** ```js { "success": true, "result": "87c10767531071732fe15e6c34cf275900c342a3" } ``` * `success`: Boolean of whether transaction has been signed and broadcasted successfully or not * `error`: Error message, if any * `result`: Transaction ID ## Sign and Broadcast Hive URI Sign and broadcast a [hive-uri](https://gitlab.syncad.com/hive/hive-uri) encoded transaction. **Usage:** ```js const result = aioha.signAndBroadcastUri('hive://sign/ops/W1sidm90ZSIseyJhdXRob3IiOiJzYWdhcmtvdGhhcmk4OCIsInBlcm1saW5rIjoiMjAyNC0wNi0wNS1kYWlseS11cGRhdGVzLWZyb20tc2FnYXJrb3RoYXJpODgiLCJ2b3RlciI6InRlY2hjb2RlcngiLCJ3ZWlnaHQiOjEwMDAwfV1d', KeyTypes.Posting) ``` Required arguments include `hive://` prefixed URI and the `KeyTypes` enum for the key type to be used (must not be `KeyTypes.Memo`). **Result:** ```js { "success": true, "result": "87c10767531071732fe15e6c34cf275900c342a3" } ``` * `success`: Boolean of whether transaction has been signed and broadcasted successfully or not * `error`: Error message, if any * `result`: Transaction ID ## Getters ### Registered Providers List all registered providers. ```js aioha.getProviders() // ['keychain', 'peakvault', 'ledger', 'hiveauth', 'hivesigner'] ``` ### Current Provider Returns one of the providers above that the user logs in with. ```js aioha.getCurrentProvider() ``` ### Current User Returns logged in Hive username. ```js aioha.getCurrentUser() ``` ### Is Logged In Returns a boolean of whether a user is logged in or not. ```js aioha.isLoggedIn() ``` ### Is Provider Registered Returns a boolean of whether the specified provider is registered. This does not indicate that the said provider is available to the user (i.e. due to required browser extension not installed). ```js aioha.isProviderRegistered(Providers.HiveAuth) ``` ### Is Provider Enabled Returns a boolean of whether the specified provider is registered and available for use by the user. ```js aioha.isProviderEnabled(Providers.HiveAuth) ``` ### List Other Users Returns a list of other authenticated users and the corresponding provider that are currently inactive. **Usage:** ```js aioha.getOtherLogins() ``` **Result:** ```json { "user1": "keychain", "user2": "peakvault" } ``` ### Get Login Expiration Retrieve the login expiration timestamp (in epoch milliseconds) for a username. > **Info** > > This is for **HiveAuth** and **HiveSigner** providers as the authentication expires. This method will return `undefined` for other providers that do not expire. ```js aioha.getLoginExpiration('someone') ``` ### Get Hive Chain ID Retrieve the current Hive chain ID used to sign transactions. > **Note** > > Currently only **Ledger** and **MetaMask Snaps** providers respect this setting. Hive Keychain uses the chain ID of the API node set by the user in the extension. ```js aioha.getChainId() ``` ## Setters ### Set Hive API Set a Hive API node for RPC calls made by Aioha (i.e. HIVE/VEST calculation, broadcast transaction) ```js aioha.setApi('https://techcoderx.com', [ 'https://api.hive.blog', 'https://api.openhive.network', // ...more fallback nodes ]) ``` ### Set Hive Chain ID Set a custom Hive chain ID when signing transactions for testnets. > **Note** > > Currently only **Ledger** and **MetaMask Snaps** providers respect this setting. Hive Keychain uses the chain ID of the API node set by the user in the extension. ```js // default testnet aioha.setChainId('18dcf0a285365fc58b71f18b3d3fec954aa0c141c44e4e5cb4cf777b9eab274e') ``` ### Set Magi Network ID Set Magi network ID for Magi related functions. ```js aioha.vscSetNetId('vsc-mainnet') ``` ## Operations The return value for these method calls will be equivalent to sign and broadcast transaction result [above](#sign-and-broadcast-transaction). ### Social **Vote:** ```js const vote = await aioha.vote('author', 'permlink', 10000) const voteMany = await aioha.voteMany([ { author: 'author1', permlink: 'permlink1', weight: 10000 }, { author: 'author2', permlink: 'permlink2', weight: 5000 } ]) ``` **Comment:** ```js const comment = await aioha.comment('pa', 'pp', 'permlink', 'title', 'body', { foo: 'bar' }) const commentWithOptions = await aioha.comment('pa', 'pp', 'permlink', 'title', 'body', { foo: 'bar' }, { author: aioha.getCurrentUser(), permlink: 'permlink', max_accepted_payout: '1000000.000 HBD', percent_hbd: 10000, allow_votes: true, allow_curation_rewards: true, extensions: [[0, { beneficiaries: [ { account: 'alice', weight: 100 }, { account: 'bob', weight: 150 } ] }]] }) ``` **Delete Comment:** The post or comment must contain no child comments or positive rshares. ```js const deleted = await aioha.deleteComment('permlinktodel') ``` **Reblog:** ```js const reblog = await aioha.reblog('author', 'permlink', true) const removeReblog = await aioha.reblog('author', 'permlink', false) ``` **Follow/Ignore:** ```js const follow = await aioha.follow('author', true) const unfollow = await aioha.follow('author', false) const ignored = await aioha.ignore('authortoignore') ``` ### Custom JSON Publish an arbitrary JSON data. ```js const customJson = await aioha.customJSON(KeyTypes.Posting, 'my-id', { foo: 'bar' }, 'Display Title') ``` ### Claim Rewards Claim Hive rewards that have paid out from content reward pool. ```js const rewardClaim = await aioha.claimRewards() ``` ### Transfer Transfer HIVE/HBD to another account. **One Time:** ```js // transfer 1 HIVE with memo const xfer = await aioha.transfer('recipient', 1, Asset.HIVE, 'Transferred using Aioha with memo') // transfer 1 HBD without memo const xferNoMemo = await aioha.transfer('recipient2', 1, Asset.HBD) ``` **Recurrent:** ```js // start recurrent transfer of 1 HIVE every 24 hours for 7 days to 'bob' const recurrentXfer = await aioha.recurrentTransfer('bob', 1, Asset.HIVE, 24, 7, 'Transferred using Aioha with memo') // cancel HIVE recurrent transfer to 'bob' const cancelRecurrentXfer = await aioha.cancelRecurrentTransfer('bob', Asset.HIVE, 'Another memo for cancel recurrent transfer') ``` ### HIVE staking **Stake:** ```js // stake 1000 HIVE to itself const stake = await aioha.stakeHive(1000) // stake 1000 HIVE to another account const stakeToOtherAccount = await aioha.stakeHive(1000, 'anotheraccount') ``` **Unstake:** ```js // unstake 1000 HIVE of vesting shares const unstake = await aioha.unstakeHive(1000) // unstake 1 million vesting shares const unstakeByVests = await aioha.unstakeHiveByVests(1000000) // cancel unstake const cancelUnstake = await aioha.unstakeHive(0) ``` **Delegate:** ```js // delegate 1000 HIVE of vesting shares const delegate = await aioha.delegateStakedHive('delegatee', 1000) // delegate 1 million vesting shares const delegateByVests = await aioha.delegateVests('delegatee', 1000000) // undelegate from delegatee const undelegate = await aioha.delegateStakedHive('delegatee', 0) ``` ### Governance **Vote Witness:** ```js const voteWitness = await aioha.voteWitness('thewitness', true) const unvoteWitness = await aioha.voteWitness('thewitness', false) ``` **Vote Proposals:** ```js const voteProposals = await aioha.voteProposals([0], true) const unvoteProposals = await aioha.voteProposals([0], false) ``` **Set/Clear Proxy:** ```js const setProxy = await aioha.setProxy('proxiedacc') const clearProxy = await aioha.clearProxy() ``` ### Authority **Account Auths:** ```js // Add 'aioha' account to posting auths with weight 1 const addAccount = await aioha.addAccountAuthority('aioha', KeyTypes.Posting, 1) // Remove 'aioha' account from posting auths const rmAccount = await aioha.removeAccountAuthority('aioha', KeyTypes.Posting) ``` **Key Auths:** ```js // Add 'STMxxxx' public key to posting auths with weight 1 const addKey = await aioha.addKeyAuthority('STMxxxx', KeyTypes.Posting, 1) // Remove 'STMxxxx' public key from posting auths const rmKey = await aioha.removeKeyAuthority('STMxxxx', KeyTypes.Posting) ``` ### Magi Call Transact on Magi from L1, such as calling a smart contract, transfer or withdraw L2 balances. > **Warning** > > These methods will be moved to the upcoming Magi TypeScript library and will be removed from Aioha core in v2. **Transfer:** ```js const transfer = await aioha.vscTransfer('hive:bob', 1, Asset.HIVE, 'Optional memo') ``` **Withdraw:** ```js const transfer = await aioha.vscWithdraw('hive:charlie', 1, Asset.HIVE, 'Optional memo') ``` **Stake:** ```js // Stake 2,000 HIVE for consensus const consensusStake = await aioha.vscStake(VscStakeType.Consensus, 2000) // Stake 100 HBD const hbdStake = await aioha.vscStake(VscStakeType.HBD, 100) ``` **Unstake:** ```js // Unstake 2,000 HIVE from consensus const consensusStake = await aioha.vscUnstake(VscStakeType.Consensus, 2000) // Unstake 100 HBD const hbdStake = await aioha.vscUnstake(VscStakeType.HBD, 100) ``` **Call Contract:** ```js // Call testLog at contract vsc1Bp8y... with 100 RC limit, no intents, signed with active authority const contractCall = await aioha.vscCallContract('vsc1Bp8ykBKDT74vYrZShhfEhp8Mn8bG2ChiAf', 'testLog', 'tx payload', 100, [], KeyTypes.Active) ``` --- # Wax Signer Aioha may be used to sign transactions on [Wax Typescript](https://gitlab.syncad.com/hive/wax/-/tree/develop/ts?ref_type=heads) library. > **Note** > > ***Wax*** here refers to the feature-rich multi-language [library](https://gitlab.syncad.com/hive/wax) for [Hive](https://hive.io). Not to be confused with the competing L1 blockchain forked from EOS. ## Usage ```js import { createHiveChain } from '@hiveio/wax' import { initAioha, KeyTypes, WaxAiohaSigner } from '@aioha/aioha' const aioha = initAioha() const chain = await createHiveChain() const tx = await chain.createTransaction() const provider = WaxAiohaSigner.for(aioha, 'active') // push some operations here... await provider.signTransaction(tx) await chain.broadcast(tx) ``` ## Caveats Currently most providers produce signatures using the legacy serialization method, while Wax prefers the newer method supported as of HF26 which saves 4 bytes per asset/NAI value in the transaction. In addition, Aioha primarily uses the legacy transaction JSON format (array tuple of `["operation_name", {...body}]`) while Wax primarily uses the newer JSON format of `{"type": "operation_name", value: {...body}}`. --- # FAQ ## 1. Why is plaintext private key login not supported? Plaintext private keys are (and should be) a thing of the past, the closest provider would be HiveSigner. Even in web2 passkeys have replaced passwords. With that said there is a [BeekeeperProvider](https://github.com/aioha-hive/aioha/blob/main/src/providers/custom/beekeeper.ts) ([usage](/docs/core/custom-provider.md#beekeeper-signer)) but it should only be used for backends. --- ## [📄️ Lit](/docs/framework/lit.md) [Lit provider for Aioha that uses Lit's context to provide reactive states in your Lit Element components.](/docs/framework/lit.md) --- # Lit Provider Lit provider for Aioha that uses Lit's [context](https://lit.dev/docs/data/context) to provide reactive states in your Lit Element components. ## Installation ```sh pnpm i @aioha/providers @aioha/aioha ``` ## Usage 1. Initialize Aioha and setup provider at the root of your application. This is usually done at the entrypoint file (i.e. `index.ts` or `my-element.ts`). ```ts // src/my-element.ts import { LitElement, html } from 'lit' import { customElement } from 'lit/decorators.js' import { Aioha } from '@aioha/aioha' import '@aioha/providers/lit' @customElement('my-element') export class MyElement extends LitElement { aioha: Aioha = new Aioha() connectedCallback() { super.connectedCallback() // See options: https://aioha.dev/docs/core/usage#instantiation this.aioha.setup() } render() { return html` ` } } ``` 2. Use Aioha anywhere within `` through the `@consume` decorator. ```ts // src/components/aioha-page.ts import { LitElement, html } from 'lit' import { customElement, state } from 'lit/decorators.js' import { consume } from '@lit/context' import { AiohaCtx, ProviderCtx, UserCtx, OtherUsersCtx } from '@aioha/providers/lit' import type { Aioha, Providers, PersistentLoginProvs } from '@aioha/aioha' @customElement('aioha-page') export class AiohaPage extends LitElement { @consume({ context: AiohaCtx }) @state() private aioha?: Aioha @consume({ context: UserCtx, subscribe: true }) @state() private _user?: string @consume({ context: ProviderCtx, subscribe: true }) @state() private _prov?: Providers @consume({ context: OtherUsersCtx, subscribe: true }) @state() private _otherUsers: PersistentLoginProvs = {} render() { return html`

User: ${this._user} Provider: ${this._prov}

${Object.keys(this._otherUsers).map((u) => { return html`

Other user: ${u} Provider: ${this._otherUsers[u]}

` })}
` } } ``` ## Using Events Listen for [events](/docs/core/jsonrpc.md#events) using lifecycle callbacks. ```ts // src/components/some-element.ts import { LitElement, html } from 'lit' import { customElement, state } from 'lit/decorators.js' import { consume } from '@lit/context' import { AiohaCtx } from '@aioha/providers/lit' import { Aioha } from '@aioha/aioha' @customElement('some-element') export class SomeElement extends LitElement { @consume({ context: AiohaCtx }) @state() private aioha?: Aioha private _handler() { // handle your event here } connectedCallback() { super.connectedCallback() this.aioha!.on('sign_tx_request', this._handler) } disconnectedCallback() { super.disconnectedCallback() this.aioha!.off('sign_tx_request', this._handler) } render() { return html`` } } ``` --- # React Provider For React apps that are **not** using the [ready-made UI](/docs/react/ui.md). > **Note** > > This module is used and provided in the ready-made UI so there is no need to install this separately. ## Installation ```sh pnpm i @aioha/providers @aioha/aioha ``` ## Usage 1. Initialize Aioha and setup the provider at the root of your application. This may be in `index.jsx`, `index.tsx` or `App.tsx` depending on the framework you use. ```tsx import { initAioha } from '@aioha/aioha' import { AiohaProvider } from '@aioha/providers/react' // See options: https://aioha.dev/docs/core/usage#instantiation const aioha = initAioha() const App = () => { return ( ) } ``` 2. Use Aioha anywhere within `AiohaProvider` through `useAioha()`. ```tsx import { useAioha } from '@aioha/providers/react' export const AiohaPage = () => { const { aioha, user, provider, otherUsers } = useAioha() // rest of your page goes here } ``` Logged in username and provider may be retrieved through `user` and `provider` variables. Other authenticated accounts can be retrieved through `otherUsers` variable. ## Using Events Listen for [events](/docs/core/jsonrpc.md#events) in `useEffect` hook. ```tsx import { useEffect } from 'react' import { useAioha } from '@aioha/providers/react' export const YourComponent = () => { const { aioha } = useAioha() useEffect(() => { const handler = () => { // handle your event here } aioha.on('sign_tx_request', handler) return () => { aioha.off('sign_tx_request', handler) } }, []) } ``` ## SSR Apps If you are using a framework that uses SSR (server-side rendering) such as Next.js, you may need to setup Aioha separately in a `useEffect()`. ```tsx import React, { useEffect } from 'react' import { Aioha } from '@aioha/aioha' import { AiohaProvider } from '@aioha/providers/react' const aioha = new Aioha() const App = () => { useEffect(() => { // See options: https://aioha.dev/docs/core/usage#instantiation aioha.setup() }, []) return ( ) } ``` ## Migrating to >=1.8.0 The React provider is now part of a single unified package that consists of all framework providers. Uninstall the old React provider and install the new package: ```sh pnpm un @aioha/react-provider pnpm i @aioha/providers ``` Update the imports accordingly: ```ts // From: import { AiohaProvider, useAioha } from '@aioha/react-provider' // To: import { AiohaProvider, useAioha } from '@aioha/providers/react' ``` --- # Svelte Provider Svelte provider for Aioha that uses Svelte's [context API](https://svelte.dev/docs/svelte/context) to provide reactive states in your Svelte components. ## Installation ```sh pnpm i @aioha/providers @aioha/aioha ``` ## Usage 1. Initialize Aioha and setup provider at the root of your application. This is usually done in `App.svelte`. ```html ``` 2. Use Aioha anywhere within `AiohaProvider` through Svelte's `getContext()` function. ```html

Current User: {ctx.user}

Current Provider: {ctx.provider}

{#each Object.entries(ctx.otherUsers) as [key, value]}

{key}: {value}

{/each} ``` ## Using Events Listen for [events](/docs/core/jsonrpc.md#events) using lifecycle functions within Svelte components. ```html ``` ## SSR Apps If you are using a framework that uses SSR (server-side rendering) such as SvelteKit, you may need to setup Aioha separately in a `onMount()`. ```html ``` --- # Vue Provider Vue provider for Aioha that uses Vue's [provide/inject](https://vuejs.org/guide/components/provide-inject.html) to provide reactive states in your Vue components. ## Installation ```sh pnpm i @aioha/providers @aioha/aioha ``` ## Usage 1. Initialize Aioha and setup provider at the root of your application. This is usually done in `App.vue`. ```html ``` 2. Use Aioha anywhere within `AiohaProvider` through the `useAioha()` composable. ```html ``` Alternatively using Vue's `inject()` function: ```html ``` ## Using Events Listen for [events](https://aioha.dev/docs/core/jsonrpc#events) using lifecycle hooks within Vue's composition API. ```html ``` ## SSR Apps If you are using a framework that uses SSR (server-side rendering) such as Nuxt, you may need to setup Aioha separately in a `onMounted()`. ```html ``` --- # Overview Magi JS is JavaScript/TypeScript library for interacting with the [Magi network](https://magi.eco) with wallet interoperability features. ## Supported Wallet Types * Hive (through [Aioha](/docs.md)) * Ethereum (through [Viem](https://viem.sh)) * Bitcoin (any wallet exposing `signMessage`, e.g. [Leather](https://leather.io), [Xverse](https://www.xverse.app), [UniSat](https://unisat.io)) * View-only (read account state without any signer; signing operations return an error) ## Installation **npm:** ```sh npm i @aioha/magi @aioha/aioha viem ``` **yarn:** ```sh yarn add @aioha/magi @aioha/aioha viem ``` **pnpm:** ```sh pnpm i @aioha/magi @aioha/aioha viem ``` ## Usage Example ```js import { Magi, Wallet, Asset } from '@aioha/magi' import { createWalletClient, http } from 'viem' import { initAioha } from '@aioha/aioha' const magi = new Magi() // Set options magi.setApi('https://magi-test.techcoderx.com/api/v1/graphql', []) magi.setNetId('vsc-testnet') // Ethereum wallet const viemClient = createWalletClient({ account: '0x2540009A1027e5c6aa0274db4fEF2622aC2B200C', transport: http('http://127.0.0.1:1248') }) magi.setViem(viemClient) // Bitcoin wallet magi.setBitcoin({ address: 'bc1qYourAddress', signMessage: (msg) => myBtcWallet.signMessage(msg) }) // Hive wallet const aioha = initAioha() magi.setAioha(aioha) // Set wallet type to use magi.setWallet(Wallet.Ethereum) // Transfer 1 HBD const xfer = await magi.transfer('did:pkh:eip155:1:0xrecipient', 1, Asset.hbd, '') // Call contract const dumpEnv = await magi.call('vsc1contractid', 'method', 'payload', 200, []) // Sign and broadcast multi-op transaction const call = await magi.signAndBroadcastTx([ { type: 'call', payload: { contract_id: 'vsc1BX9RbKJYAqSSZZzrGeBWyBZkG3ojTVPRoo', action: 'dumpEnv', payload: '', rc_limit: 300, intents: [] } } ]) ``` --- # Framework Providers Reactive state providers for Magi JS, available for React, Vue, Svelte and Lit. These work similarly to the [Aioha core framework providers](/docs/framework.md) but expose Magi-specific state. The providers automatically track: * `user` — the connected user address (updates on wallet switch, Hive account change, and EVM account change) * `wallet` — the active `Wallet` enum (`Wallet.Hive`, `Wallet.Ethereum`, `Wallet.Bitcoin`, or `Wallet.ViewOnly`) > **Info** > > `MagiProvider` must be nested inside the corresponding [Aioha framework provider](/docs/framework.md) (e.g. `AiohaProvider`) so it can consume Hive wallet state automatically. For EVM wallet support, [Wagmi](https://wagmi.sh) integration is required — see each framework section below for details. > **View-only wallets** > > View-only is driven imperatively on the shared `magi` instance — no provider prop. Call `magi.setViewOnly(did)` and `magi.setWallet(Wallet.ViewOnly)` at any time and the `user` / `wallet` context will update via the `wallet_changed` event. Calling `setViewOnly(newDid)` again while `Wallet.ViewOnly` is active also re-emits `wallet_changed`, so UI bindings pick up the new DID automatically. ## Installation ```sh pnpm i @aioha/providers @aioha/magi @aioha/aioha ``` For EVM wallet support, also install [Wagmi](https://wagmi.sh/react/getting-started) and its dependencies for your framework. ## React ### Setup Initialize Magi and wrap your app with `MagiProvider` inside both `AiohaProvider` and Wagmi's `WagmiProvider`. ```tsx import { initAioha } from '@aioha/aioha' import { Magi } from '@aioha/magi' import { AiohaProvider } from '@aioha/providers/react' import { MagiProvider } from '@aioha/providers/magi/react' import { WagmiProvider } from 'wagmi' import { QueryClientProvider, QueryClient } from '@tanstack/react-query' const aioha = initAioha() const magi = new Magi() magi.setAioha(aioha) const queryClient = new QueryClient() const App = () => { return ( ) } ``` `MagiProvider` consumes `AiohaProvider` context for Hive wallet state and uses Wagmi's `useConnectorClient()` hook for EVM wallet state. See the [Wagmi React docs](https://wagmi.sh/react/getting-started) for `wagmiConfig` setup. ### Consuming State Use the `useMagi()` hook anywhere within the provider. ```tsx import { useMagi } from '@aioha/providers/magi/react' export const MagiPage = () => { const { magi, user, wallet } = useMagi() return (

User: {user}

Wallet: {wallet}

) } ``` ### SSR Apps For frameworks like Next.js, initialize Aioha inside a `useEffect()`. ```tsx import React, { useEffect } from 'react' import { Aioha } from '@aioha/aioha' import { Magi } from '@aioha/magi' import { AiohaProvider } from '@aioha/providers/react' import { MagiProvider } from '@aioha/providers/magi/react' import { WagmiProvider } from 'wagmi' import { QueryClientProvider, QueryClient } from '@tanstack/react-query' const aioha = new Aioha() const magi = new Magi() magi.setAioha(aioha) const queryClient = new QueryClient() const App = () => { useEffect(() => { aioha.setup() }, []) return ( ) } ``` ## Vue ### Setup Initialize Magi and wrap your app with `MagiProvider` inside both `AiohaProvider` and Wagmi's Vue plugin. ```html ``` `MagiProvider` consumes `AiohaProvider` context for Hive wallet state and uses Wagmi's `useConnectorClient()` composable for EVM wallet state. See the [Wagmi Vue docs](https://wagmi.sh/vue/getting-started) for Wagmi plugin setup. ### Consuming State Use the `useMagi()` composable. ```html ``` Alternatively, use Vue's `inject()` with the exported injection keys: ```html ``` ### SSR Apps For frameworks like Nuxt, initialize Aioha in `onMounted()`. ```html ``` ## Svelte ### Setup Initialize Magi and wrap your app with `MagiProvider` inside `AiohaProvider`. Pass `wagmiConfig` for EVM wallet support. ```html ``` `MagiProvider` consumes `AiohaProvider` context for Hive wallet state. The optional `wagmiConfig` prop enables EVM wallet tracking via `@wagmi/core`. See the [Wagmi Core docs](https://wagmi.sh/core/getting-started) for config setup. ### Consuming State Use Svelte's `getContext()` with the exported `MagiCtxKey`. ```html

User: {ctx.user}

Wallet: {ctx.wallet}

``` ### SSR Apps For frameworks like SvelteKit, initialize Aioha in `onMount()`. ```html ``` ## Lit ### Setup Initialize Magi and nest `` inside ``. Pass `.wagmiConfig` for EVM wallet support. ```ts // src/my-element.ts import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' import { Aioha } from '@aioha/aioha' import { Magi } from '@aioha/magi' import { type Config } from '@wagmi/core' import '@aioha/providers/lit' import '@aioha/providers/magi/lit' @customElement('my-element') export class MyElement extends LitElement { aioha: Aioha = new Aioha() magi: Magi = new Magi() @property({ attribute: false }) wagmiConfig?: Config connectedCallback() { super.connectedCallback() this.aioha.setup() this.magi.setAioha(this.aioha) } render() { return html` ` } } ``` `` consumes `` context for Hive wallet state. The optional `.wagmiConfig` property enables EVM wallet tracking via `@wagmi/core`. See the [Wagmi Core docs](https://wagmi.sh/core/getting-started) for config setup. ### Consuming State Use the `@consume` decorator with the exported contexts. ```ts // src/components/magi-page.ts import { LitElement, html } from 'lit' import { customElement, state } from 'lit/decorators.js' import { consume } from '@lit/context' import { MagiCtx, MagiUserCtx, MagiWalletCtx } from '@aioha/providers/magi/lit' import type { Magi, Wallet } from '@aioha/magi' @customElement('magi-page') export class MagiPage extends LitElement { @consume({ context: MagiCtx }) @state() private magi?: Magi @consume({ context: MagiUserCtx, subscribe: true }) @state() private _user?: string @consume({ context: MagiWalletCtx, subscribe: true }) @state() private _wallet?: Wallet render() { return html`

User: ${this._user}

Wallet: ${this._wallet}

` } } ``` > **Note** > > Use `subscribe: true` on `MagiUserCtx` and `MagiWalletCtx` to receive reactive updates when the user or wallet changes. --- # Usage Configure and interact with Magi in 5 minutes or less. ## Instantiation Create an instance of Magi. Returns a `Magi` object. ```js import { Magi } from '@aioha/magi' const magi = new Magi() ``` ## Enums **Wallet:** The `Wallet` enum exported are as follows: ```js export enum Wallet { Hive = 'hive', Ethereum = 'evm', Bitcoin = 'btc', ViewOnly = 'viewonly' } ``` **Asset:** The `Asset` enum exported are as follows: ```js export enum Asset { hive = 'hive', hbd = 'hbd', shbd = 'hbd_savings' } ``` **KeyTypes:** The `KeyTypes` enum exported are as follows: ```js export enum KeyTypes { Posting = 'posting', Active = 'active' } ``` > **Note** > > `KeyTypes` is only applicable for Hive wallets. Ethereum and Bitcoin wallets always sign with the account's key. ## Register Wallets ### Hive Wallet Register a Hive wallet using an [Aioha](https://aioha.dev) instance. ```js import { initAioha } from '@aioha/aioha' const aioha = initAioha() magi.setAioha(aioha) ``` ### Ethereum Wallet Register an Ethereum wallet using a [Viem](https://viem.sh) wallet client. ```js import { createWalletClient, http } from 'viem' const viemClient = createWalletClient({ account: '0xYourAddress', transport: http('https://your-rpc-url') }) magi.setViem(viemClient) ``` ### Bitcoin Wallet Register a Bitcoin wallet by providing a `BtcClient` object. This is a minimal interface that any Bitcoin wallet can implement. ```js import { BtcClient } from '@aioha/magi' ``` The `BtcClient` interface: ```ts interface BtcClient { address: string signMessage(message: string): Promise } ``` * `address` — Bitcoin address (P2PKH `1...`, P2SH-P2WPKH `3...`, or P2WPKH `bc1q...`) * `signMessage` — Signs a message string and returns a base64-encoded signature (BIP-137 or BIP-322) > **Note** > > Taproot (`bc1p...`) addresses are not currently supported. ```js magi.setBitcoin({ address: 'bc1qYourAddress', signMessage: (msg) => myBtcWallet.signMessage(msg) }) ``` Example: Reown AppKit If you use [Reown AppKit](https://reown.com/appkit) with the [Bitcoin adapter](https://docs.reown.com/appkit/bitcoin/core/installation), you can create a `BtcClient` from the AppKit modal: ```js import { createAppKit } from '@reown/appkit' import { BitcoinAdapter } from '@reown/appkit-adapter-bitcoin' const bitcoinAdapter = new BitcoinAdapter({ projectId }) const modal = createAppKit({ adapters: [bitcoinAdapter], // ... }) // After wallet connection: const provider = modal.getProvider('bip122') magi.setBitcoin({ address: modal.getAddress(), signMessage: (msg) => provider.signMessage({ message: msg, address: modal.getAddress(), protocol: 'ECDSA' }) }) ``` Example: UniSat ```js const unisat = window.unisat const accounts = await unisat.requestAccounts() magi.setBitcoin({ address: accounts[0], signMessage: (msg) => unisat.signMessage(msg) }) ``` ### View-only Wallet Register a view-only wallet by passing a prefixed DID. This lets you display an account's state without ever holding a signer — useful for dashboards, block explorers, or preview UIs. ```js // Hive account magi.setViewOnly('hive:alice') // Ethereum account magi.setViewOnly('did:pkh:eip155:1:0xYourAddress') // Bitcoin account magi.setViewOnly('did:pkh:bip122:000000000019d6689c085ae165831e93:bc1qYourAddress') ``` The prefix determines the chain: | Prefix | Chain | | -------------------------------------------------- | -------- | | `hive:` | Hive | | `did:pkh:eip155:1:` | Ethereum | | `did:pkh:bip122:000000000019d6689c085ae165831e93:` | Bitcoin | Any other input throws an `Error`. > **Note** > > All signing and broadcast operations on a view-only wallet return an `OperationError` with code `4200` and the message `Cannot sign or transact in view only mode`. `getUser()`, `isConnected()`, and `getWallet()` work as usual. ## Set Active Wallet Set the wallet type to use for transactions. This must be called before performing any operations. ```js import { Wallet } from '@aioha/magi' magi.setWallet(Wallet.Ethereum) // or magi.setWallet(Wallet.Hive) // or magi.setWallet(Wallet.Bitcoin) // or magi.setWallet(Wallet.ViewOnly) ``` ## Setters ### Set Magi API Set the Magi GraphQL API URL. An optional array of fallback API URLs may be provided. ```js magi.setApi('https://vsc.techcoderx.com/api/v1/graphql', [ 'https://api.vsc.eco/api/v1/graphql', 'https://vsc.atexoras.com:2087/api/v1/graphql' ]) ``` The URL must start with `http://` or `https://`. The default API is `https://vsc.techcoderx.com/api/v1/graphql` with built-in fallback nodes. ### Set Network ID Set the Magi network ID. Defaults to `vsc-mainnet`. ```js magi.setNetId('vsc-mainnet') // For testnet magi.setNetId('vsc-testnet') ``` ## Getters ### Get API Returns an array of API endpoints, where the first item is the main endpoint and the remaining are fallbacks. ```js magi.getApi() // ['https://vsc.techcoderx.com/api/v1/graphql', ...] ``` ### Get Network ID Returns the current Magi network ID. ```js magi.getNetId() // 'vsc-mainnet' ``` ### Get Connected User Returns the connected user address, or `undefined` if not connected. ```js // Without prefix magi.getUser() // 'alice', '0xYourAddress', or 'bc1q...' // With prefix magi.getUser(true) // 'hive:alice' // 'did:pkh:eip155:1:0xYourAddress' // 'did:pkh:bip122:000000000019d6689c085ae165831e93:bc1q...' ``` ### Get Active Wallet Returns the `Wallet` enum of the wallet type currently in use, or `undefined` if not set. ```js magi.getWallet() // Wallet.Hive, Wallet.Ethereum, Wallet.Bitcoin, or Wallet.ViewOnly ``` ### Is Connected Returns a boolean of whether a wallet is connected. ```js magi.isConnected() // true or false ``` ## Sign and Broadcast Transaction Sign and broadcast a Magi transaction consisting of one or more operations. **Usage:** ```js const result = await magi.signAndBroadcastTx([ { type: 'call', payload: { contract_id: 'vsc1BX9RbKJYAqSSZZzrGeBWyBZkG3ojTVPRoo', action: 'dumpEnv', payload: '', rc_limit: 300, intents: [] } } ], KeyTypes.Active) ``` * `tx`: Array of `MagiOperation` objects * `keyType` *(optional)*: `KeyTypes.Active` or `KeyTypes.Posting` — only applicable for Hive wallets, defaults to `KeyTypes.Active` **Result:** ```js { "success": true, "result": "bafyreib5af..." } ``` * `success`: Boolean of whether the transaction was signed and broadcasted successfully * `error`: Error message, if any * `result`: Transaction CID ## Operations The return value for these method calls will be equivalent to sign and broadcast transaction result [above](#sign-and-broadcast-transaction). ### Call Contract Call a Magi smart contract. ```js // Call testLog at contract vsc1Bp8y... with 100 RC limit, no intents const result = await magi.call('vsc1Bp8ykBKDT74vYrZShhfEhp8Mn8bG2ChiAf', 'testLog', { foo: 'bar' }, 100, []) // With posting key (Hive wallets only) const result = await magi.call('vsc1Bp8ykBKDT74vYrZShhfEhp8Mn8bG2ChiAf', 'testLog', { foo: 'bar' }, 100, [], KeyTypes.Posting) ``` * `contractId`: The contract ID * `action`: The contract action/method to call * `payload`: The payload to pass to the contract action * `rc_limit`: The maximum RC (resource credits) to spend on this call * `intents`: Array of transaction intents * `keyType` *(optional)*: Key type for Hive wallets ### Transfer Transfer tokens to another Magi account. ```js // Transfer 1 HIVE to a Hive account const xfer = await magi.transfer('hive:bob', 1, Asset.hive) // Transfer 1 HBD to an Ethereum address with memo const xferWithMemo = await magi.transfer('did:pkh:eip155:1:0xRecipient', 1, Asset.hbd, 'Optional memo') ``` * `to`: Recipient address (prefixed with `hive:`, `did:pkh:eip155:1:`, or `did:pkh:bip122:...:`) * `amount`: Amount to transfer (must be greater than 0) * `currency`: `Asset.hive` or `Asset.hbd` * `memo` *(optional)*: Transfer memo ### Unmap Unmap tokens from Magi. ```js // Unmap 1 HIVE const wd = await magi.unmap('hive:charlie', 1, Asset.hive) // Unmap 50 HBD with memo const wdWithMemo = await magi.unmap('hive:charlie', 50, Asset.hbd, 'Optional memo') ``` * `to`: Destination address on L1 (prefixed with `hive:`, `did:pkh:eip155:1:`, or `did:pkh:bip122:...:`) * `amount`: Amount to withdraw (must be greater than 0) * `currency`: `Asset.hive` or `Asset.hbd` * `memo` *(optional)*: Withdrawal memo ### Stake Stake tokens on Magi. ```js import { VscStakeType } from '@aioha/aioha' // Stake 2,000 HIVE for consensus (to self) const consensusStake = await magi.stake(VscStakeType.Consensus, 2000) // Stake 2,000 HIVE for consensus to another account const consensusStakeOther = await magi.stake(VscStakeType.Consensus, 2000, 'hive:anotheraccount') // Stake 100 HBD const hbdStake = await magi.stake(VscStakeType.HBD, 100) ``` * `stakeType`: `VscStakeType.Consensus` for HIVE consensus staking, `VscStakeType.HBD` for HBD staking * `amount`: Amount to stake (must be greater than 0) * `to` *(optional)*: Recipient address — defaults to the connected account * `memo` *(optional)*: Stake memo ### Unstake Unstake tokens on Magi. ```js import { VscStakeType } from '@aioha/aioha' // Unstake 2,000 HIVE from consensus const consensusUnstake = await magi.unstake(VscStakeType.Consensus, 2000) // Unstake 100 HBD const hbdUnstake = await magi.unstake(VscStakeType.HBD, 100) ``` * `stakeType`: `VscStakeType.Consensus` for HIVE consensus unstaking, `VscStakeType.HBD` for HBD unstaking * `amount`: Amount to unstake (must be greater than 0) * `to` *(optional)*: Recipient address — defaults to the connected account * `memo` *(optional)*: Unstake memo ## Events Subscribe to Magi events using the `on`, `once`, and `off` methods. ### Subscribe ```js magi.on('wallet_changed', () => { console.log('Wallet type changed to:', magi.getWallet()) }) magi.on('sign_tx_request', () => { console.log('Transaction signature requested') }) ``` ### Subscribe Once The listener will be called only on the next emission of the event. ```js magi.once('sign_tx_request', () => { console.log('This fires only once') }) ``` ### Unsubscribe ```js const handler = () => console.log('wallet changed') magi.on('wallet_changed', handler) // Unsubscribe specific listener magi.off('wallet_changed', handler) // Unsubscribe all listeners for the event magi.off('wallet_changed') ``` ### Event Reference | Event | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `wallet_changed` | Emitted when the active wallet type is changed via `setWallet()`, or when `setViewOnly()` updates the DID while `Wallet.ViewOnly` is the active wallet | | `sign_tx_request` | Emitted when a transaction signature is requested from the wallet | --- # Media Resources ## Branding * [Logo SVG](https://aioha.dev/img/logo.svg) * [Social Card](https://aioha.dev/img/aioha-social-card.png) (used in [initial announcement post](https://peakd.com/hive-139531/@aioha/aioha-all-in-one-hive-authentication)) * [Roboto Mono font](https://fonts.google.com/specimen/Roboto+Mono) used in code snippets in Keynotes ## Keynote Slides * [HiveFest 9](https://drive.google.com/file/d/17qDTJBQl287PLlHrGSZ8dfL9ZWsDZQWN/view?usp=sharing) * [HiveFest 10](https://drive.google.com/file/d/1sEIkr0cUQAx9Eqg0XYksLt2Sh0Zvw5KC/view?usp=sharing) --- # React UI Opinionated React modal UI for Hive logins through Aioha, styled using [Tailwind CSS](https://tailwindcss.com). This provides a quick and easy way to bootstrap a React web app with ready to use Aioha-powered modal component. ## Installation ```sh pnpm i @aioha/react-ui @aioha/aioha ``` ## Usage 1. Initialize Aioha and setup the provider at the root of your application. This may be in index.jsx, index.tsx or App.tsx depending on the framework you use. > **Info** > > If you are using a framework that uses SSR (i.e. Next.js), you may need to setup Aioha separately in `useEffect()` as outlined [here](/docs/framework/react.md#ssr-apps). ```tsx import { initAioha } from '@aioha/aioha' import { AiohaProvider } from '@aioha/react-ui' // See options: https://aioha.dev/docs/core/usage#instantiation const aioha = initAioha() const App = () => { return ( ) } ``` 2. Use the modal component and the [`useAioha()` hook](/docs/framework/react.md#usage) anywhere within `AiohaProvider`. ```tsx import { useState } from 'react' import { useAioha, AiohaModal } from '@aioha/react-ui' import { KeyTypes } from '@aioha/aioha' import { Button, useColorMode } from '@chakra-ui/react' export const AiohaPage = () => { const { colorMode } = useColorMode() const [modalDisplayed, setModalDisplayed] = useState(false) const { user } = useAioha() return ( <>
) } ``` ### Tailwind CSS v4 When using the modal in a Tailwind CSS v4 project, add the following `@source` directive to your `index.css` file after the imports: ```css // src/index.css @import 'tailwindcss'; /* ...other imports */ @source '../node_modules/@aioha/react-ui'; ``` ### Tailwind CSS v3 When using the modal in a Tailwind CSS v3 project, add the following `content` in `tailwind.config.js`: ```js // tailwind.config.js /** @type {import('tailwindcss').Config} */ module.exports = { content: [ // ... './node_modules/@aioha/react-ui/dist/**/*.{js,ts,jsx,tsx,css}', // ... ], // ... } ``` ### Other > **Note** > > As this is a highly-opinionated component library, the appearance may or may not be different than expected due to potential CSS conflicts with the rest of your application. When not using Tailwind CSS, add the following import to the file where the `AiohaModal` component is used: ```tsx import { AiohaModal } from '@aioha/react-ui' import '@aioha/react-ui/dist/build.css' // rest of your components that uses AiohaModal ``` ## `AiohaModal` Component | Property | Required? | Description | Default | | -------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `displayed` | ✅ | Boolean of whether the modal is displayed. | *false* | | `loginTitle` | ❌ | Login title to be displayed. | Connect Wallet | | `loginHelpUrl` | ❌ | Help URL to be linked under provider selection view, if any. | *undefined* | | `loginOptions` | ✅ | Aioha login options. See available configuration [here](/docs/core/usage.md#login). | | | `discOptions` | ❌ | Account discovery options. See available configuration [here](/docs/core/usage.md#discover-accounts). | | | `arrangement` | ❌ | Display view preference of provider selection. Valid values: `list` and `grid`. | list | | `onLogin` | ❌ | Callback function to be called upon successful login, if any. Parameter contains login result as defined [here](/docs/core/usage.md#login). | | | `onClose` | ✅ | Function to be called to close the modal. | | | `imageServer` | ❌ | Image server URL for user avatar. | | | `explorerUrl` | ❌ | Hive block explorer URL. | | | `forceShowProviders` | ❌ | List of `Providers` to force show as login option, which must be registered already. Clicking on unavailable providers displayed will open the URL of the provider landing page. | *\[]* | | `language` | ❌ | Active locale code (e.g. `en`, `fr`, `ar`, `ja`). See [Localization](/docs/ui/i18n.md) for the list of bundled locales. | en | | `dir` | ❌ | Text direction: `ltr`, `rtl`, or `auto`. When `auto`, inferred from `document.dir` and the active locale. See [Localization](/docs/ui/i18n.md#right-to-left-rtl). | auto | | `messages` | ❌ | Custom [`Messages`](/docs/ui/i18n.md#the-messages-adapter) adapter to route translations through your own i18n framework (react-intl, lingui, i18next, ...). Defaults to the built-in adapter. | *undefined* | > **Warning** > > `hiveauth.cbWait` in `loginOptions` will be overriden as `AiohaModal` will handle the presentation of HiveAuth QR codes. ## Localization The modal ships with English and translations for 15 additional languages loaded on demand, full ICU MessageFormat support, RTL direction handling, and a pluggable adapter for other i18n frameworks. See [Localization](/docs/ui/i18n.md). ## HiveSigner Callback Page The callback page has been provided as a component that you can use in a route, as such: ```tsx import { createBrowserRouter, RouterProvider } from 'react-router-dom' import { HiveSignerCb } from '@aioha/react-ui' const router = createBrowserRouter([ { path: '/', element: }, { path: '/hivesigner', element: }, // ...rest of your pages ]) const Router = () => { return ( <> ) } ``` Then specify the path as the callback URL in `initAioha()`: ```ts const aioha = initAioha({ hivesigner: { app: 'ipfsuploader.app', callbackURL: window.location.origin + '/hivesigner', scope: ['login', 'posting'] }, // ...other options }) ``` --- ## [📄️ Web Components](/docs/ui/web-components.md) [The ready made modal UI is available as a framework agnostic web component that can be integrated on vanilla HTML/JS or any web framework.](/docs/ui/web-components.md) --- # Localization (i18n) `@aioha/react-ui` and `@aioha/lit-ui` share a single canonical message catalog published as `@aioha/i18n-keys`. The catalog exposes namespaced message IDs, an English default, a framework-neutral `Messages` adapter, ICU MessageFormat support for pluralization and interpolation, a pseudo-locale for testing, RTL direction detection, and bundled translations for 16 languages. ## Installation ```sh pnpm i @aioha/i18n-keys ``` Already installed transitively by `@aioha/react-ui` and `@aioha/lit-ui` — you only need to add it directly if you build your own adapter. ## Usage ### Switching language `` (React) and `` (Lit) both accept a `language` prop/attribute. The default English catalog ships bundled; additional locales are loaded on demand. ```tsx import { AiohaModal, defaultMessages } from '@aioha/react-ui' // Load a non-English catalog once at startup await defaultMessages.loadLocale('fr', (lng) => import(`@aioha/i18n-keys/build/locales/${lng}.json`).then((m) => m.default) ) ``` ```ts // aioha-page.ts import { defaultMessages } from '@aioha/lit-ui' await defaultMessages.loadLocale('fr', (lng) => import(`@aioha/i18n-keys/build/locales/${lng}.json`).then((m) => m.default) ) // In your template html`` ``` ### Bundled locales The package ships translation JSON for the following locales. Load each with dynamic import — English is always available without loading. | Locale | | | ------- | --------------------- | | `ar` | Arabic (RTL) | | `de` | German | | `en` | English (bundled) | | `es` | Spanish | | `fr` | French | | `hi` | Hindi | | `hr` | Croatian | | `it` | Italian | | `ja` | Japanese | | `ko` | Korean | | `ms` | Malay | | `pl` | Polish | | `pt` | Portuguese | | `ru` | Russian | | `zh-CN` | Chinese (Simplified) | | `zh-TW` | Chinese (Traditional) | ### Bring your own translations Pass a `Partial` to `defaultMessages.addCatalog(locale, catalog)` to extend or replace the shipped strings. Any subset is acceptable — unspecified keys fall back to English. ```ts import { defaultMessages } from '@aioha/react-ui' defaultMessages.addCatalog('en', { 'wallet.connect': 'Sign in with Hive' }) ``` ## Right-to-left (RTL) Both packages emit `dir="rtl"` on the modal root when the resolved direction is RTL, and use logical CSS properties (`ms-*`, `me-*`, `ps-*`, `pe-*`, `inset-x-*`) so layout mirrors automatically. Directional icons (back button, forward chevron) are flipped with `rtl:-scale-x-100`. The direction is resolved in this order: 1. Explicit `dir` prop (React) or `direction` attribute (Lit): `'ltr' | 'rtl' | 'auto'`. 2. If `'auto'`: `document.dir` if set to `rtl`. 3. Otherwise: the active locale's direction (Arabic, Hebrew, Persian, Urdu, and related languages resolve to `rtl`). ```tsx ``` ```html ``` ## Pseudo-locale testing A built-in `en-XA` locale wraps every English value with accented characters: `[!! Çóññéçţ Ŵàľľéţ !!]`. Use it to catch hardcoded strings, string-concatenation bugs, and layout truncation during development. ```tsx ``` ICU placeholders (`{user}`, `{count, plural, one {...} other {...}}`) are preserved. ## The `Messages` adapter If your app already ships an i18n framework, implement the `Messages` interface and pass it as `messages` on the modal. This lets translators manage Aioha's strings alongside yours, and keeps only one i18n runtime in your bundle. ```ts import type { Messages } from '@aioha/i18n-keys' interface Messages { t(id: MessageId, vars?: Record): string getLocale(): string setLocale(locale: string): void | Promise getDir(): 'ltr' | 'rtl' subscribe(listener: () => void): () => void } ``` ```tsx ``` ```html ``` The examples below show how to wire Aioha's 38 message IDs to a handful of popular frameworks. In every case, import the JSON catalogs from `@aioha/i18n-keys/build/locales/.json` — the framework becomes responsible only for formatting. ### react-intl (FormatJS) `react-intl` uses the same ICU MessageFormat syntax as Aioha's default adapter, so translations drop in unchanged. ```tsx import { createIntl, createIntlCache } from 'react-intl' import type { Messages } from '@aioha/i18n-keys' import en from '@aioha/i18n-keys/build/locales/en.json' const cache = createIntlCache() let intl = createIntl({ locale: 'en', messages: en }, cache) const listeners = new Set<() => void>() export const aiohaMessages: Messages = { t: (id, vars) => intl.formatMessage({ id }, vars), getLocale: () => intl.locale, setLocale: async (lng) => { const messages = (await import(`@aioha/i18n-keys/build/locales/${lng}.json`)).default intl = createIntl({ locale: lng, messages }, cache) listeners.forEach((l) => l()) }, getDir: () => (['ar', 'he', 'fa', 'ur'].includes(intl.locale.split('-')[0]) ? 'rtl' : 'ltr'), subscribe: (cb) => { listeners.add(cb) return () => listeners.delete(cb) } } ``` ```tsx import { AiohaModal } from '@aioha/react-ui' import { aiohaMessages } from './aioha-messages' ``` If you already have an `` in your tree, pass its `intl` object instead of creating a second one — then `t` simply becomes `(id, vars) => providedIntl.formatMessage({ id }, vars)`. ### i18next `i18next` uses `{{var}}` interpolation by default, but Aioha's catalogs use ICU `{var}`. Install [`i18next-icu`](https://github.com/i18next/i18next-icu) to parse them natively. ```ts // src/aioha-messages.ts import i18next from 'i18next' import ICU from 'i18next-icu' import type { Messages } from '@aioha/i18n-keys' import en from '@aioha/i18n-keys/build/locales/en.json' const i18n = i18next.createInstance() await i18n.use(ICU).init({ lng: 'en', fallbackLng: 'en', ns: ['aioha'], defaultNS: 'aioha', resources: { en: { aioha: en } } }) export const aiohaMessages: Messages = { t: (id, vars) => i18n.t(id, vars) as string, getLocale: () => i18n.language, setLocale: async (lng) => { if (!i18n.hasResourceBundle(lng, 'aioha')) { const res = (await import(`@aioha/i18n-keys/build/locales/${lng}.json`)).default i18n.addResourceBundle(lng, 'aioha', res) } await i18n.changeLanguage(lng) }, getDir: () => (i18n.dir() === 'rtl' ? 'rtl' : 'ltr'), subscribe: (cb) => { i18n.on('languageChanged', cb) return () => i18n.off('languageChanged', cb) } } ``` ### LinguiJS Lingui compiles ICU messages to a function map. Use the runtime `i18n` singleton and point it at Aioha's JSON: ```ts // src/aioha-messages.ts import { i18n } from '@lingui/core' import type { Messages } from '@aioha/i18n-keys' import en from '@aioha/i18n-keys/build/locales/en.json' i18n.load('en', en) i18n.activate('en') export const aiohaMessages: Messages = { t: (id, vars) => i18n._(id, vars), getLocale: () => i18n.locale, setLocale: async (lng) => { if (!i18n.messages[lng]) { const res = (await import(`@aioha/i18n-keys/build/locales/${lng}.json`)).default i18n.load(lng, res) } i18n.activate(lng) }, getDir: () => (['ar', 'he', 'fa', 'ur'].includes(i18n.locale.split('-')[0]) ? 'rtl' : 'ltr'), subscribe: (cb) => i18n.on('change', cb) } ``` ### Vue I18n For Vue apps, wrap an existing `createI18n()` instance: ```ts // src/aioha-messages.ts import { watch } from 'vue' import type { Messages } from '@aioha/i18n-keys' import i18n from './i18n' // your existing Vue i18n instance import en from '@aioha/i18n-keys/build/locales/en.json' i18n.global.mergeLocaleMessage('en', { aioha: en }) export const aiohaMessages: Messages = { t: (id, vars) => i18n.global.t(`aioha.${id}`, vars ?? {}), getLocale: () => i18n.global.locale.value, setLocale: async (lng) => { if (!i18n.global.availableLocales.includes(lng)) { const res = (await import(`@aioha/i18n-keys/build/locales/${lng}.json`)).default i18n.global.mergeLocaleMessage(lng, { aioha: res }) } i18n.global.locale.value = lng }, getDir: () => (['ar', 'he', 'fa', 'ur'].includes(i18n.global.locale.value.split('-')[0]) ? 'rtl' : 'ltr'), subscribe: (cb) => watch(i18n.global.locale, cb) } ``` Vue I18n supports ICU via the [`@intlify/core`](https://vue-i18n.intlify.dev/guide/essentials/syntax.html#message-format-syntax) engine — Aioha's `{var}` placeholders work out of the box. ### @lit/localize `@lit/localize` is a compile-time extraction system — it expects you to author `msg()` calls in source, uses positional (not named) placeholders, and does not parse [ICU MessageFormat](https://formatjs.io/docs/intl-messageformat). Routing Aioha's pre-built ICU catalog through `msg()` fights the library. The idiomatic integration is to **share locale state only**: let `@lit/localize` drive locale switching for your own strings, and keep Aioha's built-in ICU formatter for Aioha's strings. The adapter below forwards `setLocale` to `@lit/localize` and listens for `lit-localize-status` to stay in sync. ```ts // src/aioha-messages.ts import { configureLocalization, getLocale, setLocale } from '@lit/localize' import { createDefaultMessages, resolveDir } from '@aioha/i18n-keys' import type { Messages } from '@aioha/i18n-keys' // Your existing @lit/localize setup for your own strings configureLocalization({ sourceLocale: 'en', targetLocales: ['ar', 'de', 'es', 'fr', 'ja', 'zh-CN'], loadLocale: (locale) => import(`./generated/locales/${locale}.js`) }) // Aioha's built-in adapter does the ICU formatting for Aioha's catalog const aioha = createDefaultMessages({ initialLocale: getLocale() }) // Bridge: mirror @lit/localize's active locale into Aioha window.addEventListener('lit-localize-status', async (e: Event) => { const ev = e as CustomEvent<{ status: string; readyLocale?: string }> if (ev.detail.status !== 'ready' || !ev.detail.readyLocale) return const lng = ev.detail.readyLocale if (!aioha.getLocale || aioha.getLocale() === lng) return try { const cat = (await import(`@aioha/i18n-keys/build/locales/${lng}.json`)).default aioha.addCatalog(lng, cat) } catch { // locale not shipped by @aioha/i18n-keys — falls back to English } aioha.setLocale(lng) }) export const aiohaMessages: Messages = { t: (id, vars) => aioha.t(id, vars), getLocale: () => getLocale(), setLocale: async (lng) => { await setLocale(lng) // @lit/localize drives the change; the listener above syncs Aioha }, getDir: () => resolveDir(getLocale()), subscribe: aioha.subscribe } ``` ```ts // my-element.ts html`` ``` Now one call to `setLocale('fr')` switches both your `msg()` output and Aioha's modal. ### Rolling your own For any framework that isn't listed, the contract is only four responsibilities: format, read the current locale, switch it, and notify subscribers. Under the hood the built-in adapter uses [`intl-messageformat`](https://formatjs.io/docs/intl-messageformat) (FormatJS) — you can use it directly as a minimal formatter if your i18n layer doesn't ship an ICU parser. ## Message IDs See the [`@aioha/i18n-keys` README](https://github.com/aioha-hive/i18n-keys#message-ids) for the full list of the 38 message IDs, their English defaults, and ICU variables. ## Catalog versioning The catalog schema is versioned. Consumers can guard against drift: ```ts import { CATALOG_VERSION } from '@aioha/i18n-keys' if (CATALOG_VERSION !== 1) console.warn('Aioha catalog changed; review translations') ``` The version bumps on any key addition, removal, or rename. --- # Lit UI Opinionated Lit modal UI for Hive logins through Aioha, styled using [Tailwind CSS](https://tailwindcss.com). This provides a quick and easy way to bootstrap a Lit web app with ready to use Aioha-powered modal component. ## Installation ```sh pnpm i @aioha/lit-ui @aioha/aioha ``` ## CDN Import ```html ``` ## Usage 1. Initialize Aioha and setup the provider at the root of your application. This is usually done at the entrypoint file (i.e. `index.ts` or `my-element.ts`). ```ts // my-element.ts import { LitElement, html } from 'lit' import { customElement } from 'lit/decorators.js' import { Aioha } from '@aioha/aioha' import '@aioha/lit-ui' @customElement('my-element') export class MyElement extends LitElement { aioha: Aioha = new Aioha() connectedCallback() { super.connectedCallback() // See options: https://aioha.dev/docs/core/usage#instantiation this.aioha.setup() } render() { return html` ` } } ``` 2. Use the modal component and consume the [Lit context](/docs/framework/lit.md#usage) anywhere within ``. ```ts // aioha-page.ts import { UserCtx } from '@aioha/providers/lit' import { KeyTypes } from '@aioha/aioha' import type { LoginResult } from '@aioha/aioha/build/types.js' import { consume } from '@lit/context' import { LitElement, html } from 'lit' import { customElement, state } from 'lit/decorators.js' import '@aioha/lit-ui' @customElement('home-page') export class Homepage extends LitElement { @consume({ context: UserCtx, subscribe: true }) @state() private _user?: string @state() private _aiohaModalDisplayed: boolean constructor() { super() this._aiohaModalDisplayed = false } private _handleButtonClick() { this._aiohaModalDisplayed = true } render() { return html` ` } } ``` The list of `` component properties may be found [here](/docs/ui/web-components.md#initmodal-options). ## Localization The modal ships with English and translations for 15 additional languages loaded on demand, full ICU MessageFormat support, RTL direction handling, and a pluggable adapter for other i18n frameworks. See [Localization](/docs/ui/i18n.md). ## Universal Web Component The Lit modal UI can be used as a universal web component on vanilla HTML/JS or any other web framework. Details [here](/docs/ui/web-components.md). --- # Web Components The ready made modal UI is available as a framework agnostic web component that can be integrated on vanilla HTML/JS or any web framework. ## Installation ```sh pnpm i @aioha/lit-ui @aioha/aioha ``` ## CDN Import ```html ``` ## Usage ```html
``` Full example usage may be found [here](https://github.com/aioha-hive/lit-ui/blob/main/examples/index.html). ### `initModal` options | Property | Description | Default | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `displayed` | Boolean of whether the modal is displayed. | *false* | | `lightMode` | Display the modal in light mode. | *false* | | `loginTitle` | Login title to be displayed. | Connect Wallet | | `loginHelpUrl` | Help URL to be linked under provider selection view, if any. | *undefined* | | `loginOptions` | Aioha login options. See available configuration [here](/docs/core/usage.md#login). | | | `discOptions` | Account discovery options for supported providers. Details [here](/docs/core/usage.md#discover-accounts). | | | `arrangement` | Display view preference of provider selection. Valid values: `list` and `grid`. | list | | `onLogin` | Callback function to be called upon successful login, if any. Parameter contains login result as defined [here](/docs/core/usage.md#login). | | | `onClose` | Function to be called to close the modal. | | | `imageServer` | Image server URL for user avatar. | | | `explorerUrl` | Hive block explorer URL. | | | `forceShowProviders` | List of `Providers` to force show as login option, which must be registered already. Clicking on unavailable providers displayed will open the URL of the provider landing page. | *\[]* | | `language` | Active locale code (e.g. `en`, `fr`, `ar`, `ja`). See [Localization](/docs/ui/i18n.md) for the list of bundled locales. | en | | `direction` | Text direction: `ltr`, `rtl`, or `auto`. When `auto`, inferred from `document.dir` and the active locale. | auto | | `messages` | Custom [`Messages`](/docs/ui/i18n.md#the-messages-adapter) adapter for routing translations through your own i18n framework. Defaults to the built-in adapter. | *undefined* | > **Note** > > `hiveauth.cbWait` in `loginOptions` will be overriden as `AiohaModal` will handle the presentation of HiveAuth QR codes. > **Info** > > When `displayed` is specified to a reactive state, use the `onClose` method to set that reactive state to `false` to properly close the modal. All properties are optional except `loginOptions`. ## HiveSigner Callback The HiveSigner callback component has been provided that you can use in a page, as such: ```html ``` See full example [here](https://github.com/aioha-hive/lit-ui/blob/main/examples/hivesigner.html). ---