æternity Documentation Hub
Aeternity.comAeternity GitHub
  • æternity Hub
  • Developer Documentation
  • Aeternity Expansions
    • PULL_REQUEST_TEMPLATE
    • AEX X
    • AEXS
      • AEX 1
      • aex-10
      • AEX 11 Fungible Token Standard
      • AEX-130: æpps Metadata Format Specification
      • aex-141
      • AEX 2
      • AEX-3
      • AEX-4
      • AEX 5
      • AEX 7
      • AEX 8
      • aex-9
    • .github
      • ISSUE_TEMPLATE
        • aexpansion
  • AeMdw - Aeternity Middleware
    • Changelog
    • docs
      • AE MDW Architecture
      • AeMdw Hyperhain Setup Documentation
      • AeMdw Docker Setup Documentation
  • Æternity <> Ethereum Bridge
    • Changelog
  • aepp-cli-js
    • CHANGELOG
    • Contributor guide
    • reference
    • user-guide
    • .github
      • ISSUE_TEMPLATE
  • Hyperchain Bridge
    • Changelog
  • æternity's JavaScript SDK
    • Installation
      • Changelog
      • Compatibility Table
      • Quick Start
      • Transaction options
      • Development
        • Releases
      • guides
        • The range of possible address length
        • AENS (æternity naming system)
        • Batch Transactions
        • How to build a wallet
        • Connect an æpp to a wallet
        • Contract Events
        • Contracts
        • Error Handling
        • JWT usage
        • Ledger Hardware Wallet
        • Low vs High level API
        • Aeternity snap for MetaMask
        • Oracles
        • PayingForTx (Meta-Transactions)
        • Typed data hashing and signing
        • Usage with TypeScript
        • migration
          • Migration to 10.0.0
          • Migration to 11.0.0
          • Migration to 12.0.0
          • Migration to 13.0.0
          • Migration to 14.0.0
          • Migration to 7.0.0
          • Migration to 9.0.0
      • tutorials
        • vuejs
          • Vue.js HelloWorld
    • Examples
      • How to connect wallet to æpp using æternity's JS SDK
        • Sample æpp for contracts
        • iframe-based wallet
        • WebExtension-based wallet
    • .github
      • ISSUE_TEMPLATE
        • bug_report
        • feature_request
  • AEproject
    • Changelog
    • docs
      • Quick Start
      • AEproject Library
      • Migration from 3.x.x to 4.x.x
      • Migration from 4.x.x to 5.x.x
      • Upcoming Version Support
      • cli
        • Local Environment
        • Project Initialization
        • Unit Testing
    • .github
      • ISSUE_TEMPLATE
        • bug_report
        • feature_request
  • aerepl
    • Changelog
  • aescan
    • Changelog
    • Contributor Covenant Code of Conduct
    • Aescan Contributing Guide
    • LICENSE
    • .github
      • pull_request_template
      • ISSUE_TEMPLATE
        • bug_report
        • feature_request
    • docs
      • BRANCHING_STRATEGY
  • Sophia Support for Visual Studio Code
    • Changelog
  • aesophia
    • Changelog
    • Contributing to Sophia
    • docs
      • aeso_aci
      • aeso_compiler
      • Introduction
      • sophia
      • Contract examples
      • Features
      • Standard library
      • Syntax
  • aesophia_cli
    • Changelog
  • aesophia_http
    • Changelog
  • Æ Studio - Formerly known as 🔥 Fire Editor ! Aeternity's easy to use editor for writing smart contr
    • ideas
  • aeternity
    • .github
      • The Æternity Code of Conduct
      • Contributing to the Aeternity node
      • ISSUE_TEMPLATE
        • bug_report
        • feature_request
    • Welcome to Aeternity node documentation
      • Summary
      • Node API
      • Introduction
      • Build from source
      • Configuration
      • CUDA Miner
      • debian_ubuntu_packaging
      • Docker
      • Fork resistance in Aeternity nodes
      • Garbage Collection
      • Hacking the Aeternity Codebase
      • Hardware Requirements
      • hyperchains
      • Installation
      • Network Monitoring
      • Operation
      • Rebar Quick Guide
      • Stratum
      • Testing
      • Update
      • release-notes
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • About this release
        • next-ceres
          • GH-3706-micro_block_gas_limit_used_gas
          • GH-4009-allow_contract_call_by_name
          • GH-4056-add_raw_data_pointers_to_AENS
          • GH-4080-wildcard_aens_delegation_signature
          • GH3417-tamper_protection_on_ga_meta_tx
          • aega_only_allow_attach_for_fresh_accounts
          • aens_auction_adjustments
          • aens_preclaim_optional
          • deprecate_swagger
          • fate_extensions
        • next
          • GH-3088-db_direct_access-as-default
          • GH-4087_http_endpoint_info_inner_txs
          • GH4157-control-mempool-sync-start
    • Emergency patching of OTP modules
    • rosetta
    • apps
      • aehttp
        • priv
          • rosetta_README
    • data
      • aecore
        • Token migration contract
  • Hyperchains whitepaper
    • Hyperchains: Bridging Security and Scalability Through Periodic Synchronization
    • LICENSE
    • Periodically-Syncing-HyperChains
    • generations
    • Glossary
    • Hyperchain Properties
    • staking
  • æternity protocol
    • æternity naming system
    • Gossip
    • Stratum
    • SYNC
    • Serialization formats
    • State Channels
      • Off-chain
      • On-chain
      • Authentication
      • Channel off-chain state
    • æternity consensus protocol
      • Bitcoin-NG for æternity
      • Coinbase at height
      • consensus
      • Coins locking
    • Smart Contracts
      • The æternity Ethereum virtual machine (AEVM)
      • contract_state_tree
      • Contract Transactions
      • Virtual machines on the æternity blockchain
      • contracts
      • Events
      • fate
      • The Solidity Language
      • sophia
      • sophia_stdlib
    • Generalized accounts
      • Generalized accounts - explained
      • generalized_accounts
    • Oracles
      • Oracle life cycle examples
      • Oracle state tree
      • Oracle transactions
      • oracles
    • Sync
      • P2P messages
      • Mempool/TX-pool synchronization
    • node
      • æternity node API
        • Account management - intended usage
        • Encoding scheme for API identifiers and byte arrays
        • State channel WebSocket API
        • Channels - intended usage
        • Contracts - intended usage
        • Mining - intended usage
        • Naming System - intended usage
        • Oracles - intended usage
        • Spending coins - intended usage
        • examples
          • æternity node channel WebSocket API examples
            • json-rpc
              • sc_ws_basic_open_close
              • sc_ws_basic_open_close_server
              • sc_ws_broken_open_params
              • sc_ws_close_mutual
              • sc_ws_close_solo
              • sc_ws_leave_reconnect
              • sc_ws_leave_reestablish
              • sc_ws_leave_reestablish_responder_stays
              • sc_ws_leave_reestablish_wrong_fsm_id
              • sc_ws_min_depth_is_modifiable
              • sc_ws_min_depth_not_reached_timeout
              • sc_ws_opening_ping_pong
              • sc_ws_reconnect_early
              • sc_ws_slash
              • sc_ws_snapshot_solo
              • sc_ws_timeout_open
              • sc_ws_update_with_meta
              • abort_updates
                • sc_ws_abort_deposit
                • sc_ws_abort_offchain_update
                • sc_ws_abort_settle
                • sc_ws_abort_shutdown
                • sc_ws_abort_slash
                • sc_ws_abort_snapshot_solo
                • sc_ws_abort_withdraw
                • sc_ws_can_not_abort_while_open
              • assume_min_depth
                • sc_ws_basic_open_close
              • both_sign
                • init_per_group
                • conflicts
                  • sc_ws_conflict_deposit_and_offchain_update
                  • sc_ws_conflict_two_deposits
                  • sc_ws_conflict_two_offchain_updates
                  • sc_ws_conflict_two_withdrawals
                  • sc_ws_conflict_withdrawal_and_deposit
                  • sc_ws_conflict_withdrawal_and_offchain_update
              • changeable_fee
                • sc_ws_optional_params_close_solo
                • sc_ws_optional_params_create
                • sc_ws_optional_params_deposit
                • sc_ws_optional_params_settle
                • sc_ws_optional_params_slash
                • sc_ws_optional_params_snapshot
                • sc_ws_optional_params_withdrawal
                • sc_ws_set_fee_close_mutual
                • sc_ws_set_fee_close_solo
                • sc_ws_set_fee_create
                • sc_ws_set_fee_deposit
                • sc_ws_set_fee_settle
                • sc_ws_set_fee_slash
                • sc_ws_set_fee_snapshot
                • sc_ws_set_fee_withdrawal
              • changeable_fee_higher_than_gas_price
                • sc_ws_optional_params_close_solo
                • sc_ws_optional_params_create
                • sc_ws_optional_params_deposit
                • sc_ws_optional_params_settle
                • sc_ws_optional_params_slash
                • sc_ws_optional_params_snapshot
                • sc_ws_optional_params_withdrawal
              • changeable_fee_lower_than_gas_price
                • sc_ws_optional_params_close_solo
                • sc_ws_optional_params_create
                • sc_ws_optional_params_deposit
                • sc_ws_optional_params_settle
                • sc_ws_optional_params_slash
                • sc_ws_optional_params_snapshot
                • sc_ws_optional_params_withdrawal
              • changeable_gas_price
                • sc_ws_optional_params_close_solo
                • sc_ws_optional_params_create
                • sc_ws_optional_params_deposit
                • sc_ws_optional_params_settle
                • sc_ws_optional_params_slash
                • sc_ws_optional_params_snapshot
                • sc_ws_optional_params_withdrawal
              • changeable_nonce
                • sc_ws_optional_params_fail_close_mutual
                • sc_ws_optional_params_fail_close_solo
                • sc_ws_optional_params_fail_create
                • sc_ws_optional_params_fail_deposit
                • sc_ws_optional_params_fail_force_progress
                • sc_ws_optional_params_fail_settle
                • sc_ws_optional_params_fail_slash
                • sc_ws_optional_params_fail_snapshot
                • sc_ws_optional_params_fail_withdrawal
              • continuous
                • init_per_group
                • sc_ws_deposit
                • sc_ws_failed_update
                • sc_ws_generic_messages
                • sc_ws_ping_pong
                • sc_ws_update_conflict
                • sc_ws_withdraw
              • contracts
                • init_per_group
                • sc_ws_basic_contracts
                • sc_ws_environment_contract
                • sc_ws_nameservice_contract
                • sc_ws_oracle_contract
                • sc_ws_remote_call_contract
                • sc_ws_remote_call_contract_refering_onchain_data
                • sc_ws_wrong_call_data
              • force_progress
                • sc_ws_force_progress_based_on_offchain_state
                • sc_ws_force_progress_based_on_onchain_state
              • only_one_signs
                • init_per_group
                • sc_ws_conflict_on_new_offchain
                • sc_ws_conflict_snapshot_and_offchain_update
                • conflicts
                  • sc_ws_conflict_deposit_and_offchain_update
                  • sc_ws_conflict_two_deposits
                  • sc_ws_conflict_two_offchain_updates
                  • sc_ws_conflict_two_withdrawals
                  • sc_ws_conflict_withdrawal_and_deposit
                  • sc_ws_conflict_withdrawal_and_offchain_update
              • reconnect
                • sc_ws_basic_client_reconnect_i
                • sc_ws_basic_client_reconnect_i_w_reestablish
                • sc_ws_basic_client_reconnect_r
              • with_meta
                • init_per_group
                • sc_ws_deposit
                • sc_ws_remote_call_contract
                • sc_ws_withdraw
              • generalized_accounts
                • both
                  • sc_ws_basic_open_close
                • initiator
                  • sc_ws_basic_open_close
                • responder
                  • sc_ws_basic_open_close
  • Superhero Wallet
    • Changelog
    • Contributing & Guidelines
    • docs
      • Deep link URL Schema
    • .github
      • ISSUE_TEMPLATE
        • bug_report
        • feature_request
  • aerepl-web-bridge
    • AereplApi
    • aerepl_components
Powered by GitBook
On this page
  • WebExtension wallet
  • 1. Create bridge between extension and page
  • 2. Initialize AeSdkWallet class
  • iFrame-based Wallet

Was this helpful?

Export as PDF
  1. æternity's JavaScript SDK
  2. Installation
  3. guides

How to build a wallet

PreviousBatch TransactionsNextConnect an æpp to a wallet

Last updated 13 days ago

Was this helpful?

This guide shows how to build either an WebExtension Wallet or a iFrame-based Wallet.

WebExtension wallet

The full implementation of this example can be found here:

Note:

  • If you want to see a more advanced implementation you can take a look into the repository of the

1. Create bridge between extension and page

First you need to create a bridge between your extension and the page. This can be done as follows:

2. Initialize AeSdkWallet class

Then you need to initialize AeSdkWallet class in your extension and subscribe for new runtime connections. After the connection is established you can share the wallet details with the application.

iFrame-based Wallet

The iFrame-based approach works similar to the WebExtension approach except that the connectionProxy in between isn't needed.

You can take a look into the implementation of the following example to see how it works:

iFrame-based Wallet Example
WebExtension Wallet Example
Superhero Wallet
https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-web-extension/src/content-script.js#L1-L30
import browser from 'webextension-polyfill';
import {
  BrowserRuntimeConnection,
  BrowserWindowMessageConnection,
  MESSAGE_DIRECTION,
  connectionProxy,
} from '@aeternity/aepp-sdk';

(async () => {
  console.log('Waiting until document is ready');
  await new Promise((resolve) => {
    const interval = setInterval(() => {
      // TODO: ensure that there is no corresponding event
      if (document.readyState !== 'complete') return;
      clearInterval(interval);
      resolve();
    }, 100);
  });
  console.log('Document is ready');

  const port = browser.runtime.connect();
  const extConnection = new BrowserRuntimeConnection({ port });
  const pageConnection = new BrowserWindowMessageConnection({
    target: window,
    ...(window.origin !== 'null' && { origin: window.origin }),
    sendDirection: MESSAGE_DIRECTION.to_aepp,
    receiveDirection: MESSAGE_DIRECTION.to_waellet,
  });
  connectionProxy(pageConnection, extConnection);
})();
https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-web-extension/src/background.js#L1-L163
import browser from 'webextension-polyfill';
import {
  AeSdkWallet,
  CompilerHttp,
  Node,
  AccountMemory,
  BrowserRuntimeConnection,
  WALLET_TYPE,
  RpcConnectionDenyError,
  RpcRejectedByUserError,
  RpcNoNetworkById,
  unpackTx,
  unpackDelegation,
} from '@aeternity/aepp-sdk';
import { TypeResolver, ContractByteArrayEncoder } from '@aeternity/aepp-calldata';

function stringifyBigint(value) {
  return JSON.stringify(value, (k, v) => (typeof v === 'bigint' ? `${v} (as BigInt)` : v), 2);
}

let popupCounter = 0;
async function confirmInPopup(parameters) {
  const popupUrl = new URL(browser.runtime.getURL('./popup.html'));
  const popupId = popupCounter;
  popupCounter += 1;
  popupUrl.searchParams.set('data', stringifyBigint({ ...parameters, popupId }));
  await browser.windows.create({
    url: popupUrl.toString(),
    type: 'popup',
    height: 600,
    width: 600,
  });
  return new Promise((resolve) => {
    const handler = (message, sender, sendResponse) => {
      if (message.popupId !== popupId) return;
      resolve(message.response);
      sendResponse();
      browser.runtime.onMessage.removeListener(handler);
    };
    browser.runtime.onMessage.addListener(handler);
  });
}

const aeppInfo = {};
const genConfirmCallback = (action) => async (aeppId, parameters, aeppOrigin) => {
  const isConfirmed = await confirmInPopup({
    ...parameters,
    action,
    aeppId,
    aeppInfo: aeppInfo[aeppId],
    aeppOrigin,
  });
  if (!isConfirmed) throw new RpcRejectedByUserError();
};

class AccountMemoryProtected extends AccountMemory {
  async signTransaction(transaction, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
    if (id != null) {
      const opt = { ...options, transaction, unpackedTx: unpackTx(transaction) };
      if (opt.onCompiler) opt.onCompiler = '<Compiler>';
      if (opt.onNode) opt.onNode = '<Node>';
      await genConfirmCallback('sign transaction')(id, opt, aeppOrigin);
    }
    return super.signTransaction(transaction, options);
  }

  async signMessage(message, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
    if (id != null) {
      await genConfirmCallback('sign message')(id, { ...options, message }, aeppOrigin);
    }
    return super.signMessage(message, options);
  }

  async signTypedData(data, aci, { aeppRpcClientId: id, aeppOrigin, ...options }) {
    if (id != null) {
      const dataType = new TypeResolver().resolveType(aci);
      const decodedData = new ContractByteArrayEncoder().decodeWithType(data, dataType);
      const opt = {
        ...options,
        aci,
        data,
        decodedData,
      };
      await genConfirmCallback('sign typed data')(id, opt, aeppOrigin);
    }
    return super.signTypedData(data, aci, options);
  }

  async unsafeSign(data, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
    if (id != null) {
      await genConfirmCallback(`sign raw data ${data}`)(id, options, aeppOrigin);
    }
    return super.unsafeSign(data, options);
  }

  async signDelegation(delegation, { aeppRpcClientId: id, aeppOrigin, ...options }) {
    if (id != null) {
      const opt = { ...options, ...unpackDelegation(delegation) };
      await genConfirmCallback('sign delegation')(id, opt, aeppOrigin);
    }
    return super.signDelegation(delegation, options);
  }

  static generate() {
    return new AccountMemoryProtected(super.generate().secretKey);
  }
}

const aeSdk = new AeSdkWallet({
  onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
  nodes: [
    { name: 'ae_uat', instance: new Node('https://testnet.aeternity.io') },
    { name: 'ae_mainnet', instance: new Node('https://mainnet.aeternity.io') },
  ],
  accounts: [
    new AccountMemoryProtected('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf'),
    AccountMemoryProtected.generate(),
  ],
  id: browser.runtime.id,
  type: WALLET_TYPE.extension,
  name: 'Wallet WebExtension',
  async onConnection(aeppId, params, aeppOrigin) {
    const isConfirmed = await confirmInPopup({
      action: 'connect',
      aeppId,
      aeppInfo: params,
      aeppOrigin,
    });
    if (!isConfirmed) throw new RpcConnectionDenyError();
    aeppInfo[aeppId] = params;
  },
  onDisconnect(aeppId, payload) {
    console.log('Client disconnected:', aeppId, payload);
  },
  onSubscription: genConfirmCallback('subscription'),
  onAskAccounts: genConfirmCallback('get accounts'),
  async onAskToSelectNetwork(aeppId, parameters, origin) {
    await genConfirmCallback('select network')(aeppId, parameters, origin);
    if (parameters.networkId) {
      if (!this.pool.has(parameters.networkId)) throw new RpcNoNetworkById(parameters.networkId);
      await this.selectNode(parameters.networkId);
    } else {
      this.pool.delete('by-aepp');
      this.addNode('by-aepp', new Node(parameters.nodeUrl));
      await this.selectNode('by-aepp');
    }
  },
});
// The `ExtensionProvider` uses the first account by default.
// You can change active account using `selectAccount(address)` function

browser.runtime.onConnect.addListener((port) => {
  // create connection
  const connection = new BrowserRuntimeConnection({ port });
  // add new aepp to wallet
  const clientId = aeSdk.addRpcClient(connection);
  // share wallet details
  aeSdk.shareWalletInfo(clientId);
  const interval = setInterval(() => aeSdk.shareWalletInfo(clientId), 3000);
  port.onDisconnect.addListener(() => clearInterval(interval));
});

console.log('Wallet initialized!');