UNPKG

@koralabs/cardano-wallets

Version:

Library for connecting cardano wallets in the browser using CIP-30

324 lines (323 loc) 11 kB
import { WalletKey } from '../enums/WalletName'; import { Buffer } from 'buffer'; import { Blockfrost } from '../lib/blockfrost'; import { WalletError } from '../enums/WalletError'; import { loadCardanoWasm } from '../lib/serialize'; export class CardanoWallets { static _enabledWallet; static wallet; static localStorageKey = 'cardanoWallet'; static supportedWalletNames = [ WalletKey.Nami, WalletKey.Eternl, WalletKey.GeroWallet, WalletKey.Flint ]; // Private methods /** * * Used to set the wallet in local storage * * @param walletKey string */ static async _setWallet(walletKey) { window.localStorage.setItem(this.localStorageKey, JSON.stringify({ name: this.wallet.name, icon: this.wallet.icon, apiVersion: this.wallet.apiVersion, key: walletKey })); } /** * * Uses CIP-30 'enable' function to enable the wallet * * @param wallet Wallet to enable * @returns */ static _enableWallet = async (wallet) => { this._enabledWallet = await wallet.enable(); }; // Public methods /** * * Validation method used to check if the wallet is supported * * @param walletKey string */ static validateSupportedWallet(walletKey) { if (!this.supportedWalletNames.includes(walletKey)) { throw new Error(`${walletKey} is not supported. Only ${this.supportedWalletNames.join(', ')} are supported.`); } } /** * * Validation method used to check the window.cardano object * * @param walletKey string */ static validateWallet(walletKey) { if (!window) { throw new Error(WalletError.WindowNotDefined); } if (!window.cardano) { throw new Error(WalletError.NoWalletsFound); } if (!window.cardano[walletKey]) { throw new Error(WalletError.SpecificWalletNotFound); } } /** * * Used to enable the wallet and set the wallet in local storage * * @param walletKey string e.g. 'nami', 'eternal', etc * @returns an enabled wallet */ static async connect(walletKey) { this.validateSupportedWallet(walletKey); this.validateWallet(walletKey); const wallet = window.cardano[walletKey]; await this._enableWallet(wallet); this.wallet = wallet; this._setWallet(walletKey); return this.wallet; } /** * * Gets the wallet from local storage if available. If not, method will return null * * @returns an enabled wallet or null */ static async getWallet() { const wallet = window.localStorage.getItem(this.localStorageKey); if (!wallet) { return null; } const walletDetails = JSON.parse(wallet); if (!walletDetails) { return null; } return await this.connect(walletDetails.key); } static disableWallet = async () => { localStorage.removeItem(this.localStorageKey); }; // CIP-30 methods /** * * CIP-30 method to check if wallet is enabled * https://cips.cardano.org/cips/cip30/#cardanowalletnameisenabledpromisebool * * @returns CIP-30 Wallet */ static isWalletEnabled = async () => { return this.wallet.isEnabled(); }; /** * * CIP-30 method to get the balance of the wallet * https://cips.cardano.org/cips/cip30/#apigetbalancepromisecborvalue * * @returns balanceHex */ static getBalance = async () => { const balanceHex = await this._enabledWallet.getBalance(); return balanceHex; }; /** * * CIP-30 method to get the network id of the wallet * https://cips.cardano.org/cips/cip30/#apigetnetworkidpromisenumber * * @returns 0 or 1 (0 = testnet, 1 = mainnet) */ static getNetworkId = async () => { const networkId = await this._enabledWallet.getNetworkId(); return networkId; }; /** * * CIP-30 method to get the UTXOs of the wallet * https://cips.cardano.org/cips/cip30/#apigetutxosamountcborvalueundefinedpaginatepaginateundefinedpromisetransactionunspentoutputnull * * @returns an array of hex encoded utxos */ static getUtxos = async (amount, paginate) => { const rawUtxos = await this._enabledWallet.getUtxos(amount, paginate); return rawUtxos; }; /** * * CIP-30 method to get wallet collateral * https://cips.cardano.org/cips/cip30/#apigetcollateralparamsamountcborcoinpromisetransactionunspentoutputnull * * @returns list of Utxos */ static getCollateral = async () => { const collateral = await this._enabledWallet.getCollateral(); return collateral; }; /** * * CIP-30 method to get unused addresses * https://cips.cardano.org/cips/cip30/#apigetunusedaddressespromiseaddress * * @returns list of unused addresses */ static getUnusedAddresses = async () => { const unusedAddresses = await this._enabledWallet.getUnusedAddresses(); return unusedAddresses; }; /** * * CIP-30 method to get a change address * https://cips.cardano.org/cips/cip30/#apigetchangeaddresspromiseaddress * * @returns change address */ static getChangeAddress = async () => { const changeAddress = await this._enabledWallet.getChangeAddress(); return changeAddress; }; /** * * CIP-30 method to get a reward address * https://cips.cardano.org/cips/cip30/#apigetrewardaddressespromiseaddress * * @returns a reward address */ static getRewardAddresses = async () => { const rewardAddresses = await this._enabledWallet.getRewardAddresses(); const serializationLib = await loadCardanoWasm(); const bech32RewardAddress = rewardAddresses.map((addr) => serializationLib.Address.from_bytes(Buffer.from(addr, 'hex')).to_bech32()); return bech32RewardAddress; }; /** * * CIP-30 method to get a sign a transaction * https://cips.cardano.org/cips/cip30/#apisigntxtxcbortransactionpartialsignboolfalsepromisecbortransaction_witness_set * * @param tx hex encoded transaction * @param partialSign boolean * @returns */ static signTx = async (tx, partialSign = false) => { const result = await this._enabledWallet.signTx(tx, partialSign); return result; }; // public static signData = async (): Promise<string[]> => { // const rawUtxos = await this._enabledWallet.signData(); // return rawUtxos; // }; /** * * CIP-30 method to submit a transaction * https://cips.cardano.org/cips/cip30/#apisubmittxtxcbortransactionpromisehash32 * * @param tx hex encoded transaction * @returns transaction id */ static submitTx = async (tx) => { const transactionId = await this._enabledWallet.submitTx(tx); return transactionId; }; // Custom Methods /** * * Uses the CIP-30 getNetworkId to check if wallet is in mainnet or testnet * * @returns boolean */ static isMainnet = async () => { const networkId = await this._enabledWallet.getNetworkId(); return networkId === '1'; }; /** * * Used to verify that a wallet has the minimum necessary funds * * @param minimumBalance number */ static verifyBalance = async (minimumBalance) => { if (minimumBalance <= 0) { throw new Error(WalletError.MinimumBalanceIsZero); } const balanceHex = (await this.getBalance()); const serializationLib = await loadCardanoWasm(); const balance = serializationLib.Value.from_bytes(Buffer.from(balanceHex, 'hex')).coin().to_str(); if (parseInt(balance) / 1000000 <= minimumBalance) { throw new Error(WalletError.InsufficientBalance); } }; /** * * Used to verify a wallet is actively staked * * @param { rewardAddress: string } * @returns void if staked, throws NotDelegated error if not * */ static async verifyStaking({ rewardAddress }) { const result = await Blockfrost.getAccountsRegistrations(rewardAddress); if (result.error) throw new Error(WalletError.NotDelegated); if (!result.data) throw new Error(WalletError.NotDelegated); const { data: [currentRegistration] } = result; if (!currentRegistration) { throw new Error(WalletError.NotDelegated); } } /** * * Uses the serialization library to convert hex encoded utxos to human readable values * * @param rawUtxos Raw utxos from getUtxos() * @returns Utxo[] */ static async buildUtxos(rawUtxos) { const serializationLib = await loadCardanoWasm(); const utxoDetails = []; for (const rawUtxo of rawUtxos) { const utxo = serializationLib.TransactionUnspentOutput.from_bytes(Buffer.from(rawUtxo, 'hex')); const input = utxo.input(); const txIdBytes = input.transaction_id().to_bytes(); const txId = Buffer.from(txIdBytes, 'utf8').toString('hex'); const txIndx = input.index(); const output = utxo.output(); const lovelaceAmount = output.amount().coin().to_str(); const multiasset = output.amount().multiasset(); let assetNameString; let assetNameHex; let policyHex; if (multiasset) { const keys = multiasset.keys(); const keysLength = keys.len(); for (let i = 0; i < keysLength; i++) { const policy = keys.get(i); const policyBytes = policy.to_bytes(); policyHex = Buffer.from(policyBytes, 'utf8').toString('hex'); const assets = multiasset.get(policy); const assetNames = assets.keys(); const assetLenth = assetNames.len(); for (let j = 0; j < assetLenth; j++) { const assetName = assetNames.get(j); const assetNameBytes = assetName.name(); assetNameString = Buffer.from(assetNameBytes, 'utf8').toString(); assetNameHex = Buffer.from(assetNameBytes, 'utf8').toString('hex'); } } } utxoDetails.push({ txId, txIndx, lovelaceAmount, policyHex, assetNameHex, assetName: assetNameString }); } return utxoDetails; } }