UNPKG

@koralabs/cardano-wallets

Version:

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

477 lines (476 loc) 19.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.CardanoWallets = void 0; const buffer_1 = require("buffer"); const blockfrost_1 = require("../lib/blockfrost"); const WalletError_1 = require("../enums/WalletError"); const serialize_1 = require("../lib/serialize"); // import * as lib from '@emurgo/cardano-serialization-lib-asmjs/cardano_serialization_lib'; class CardanoWallets { // Private methods /** * * Used to set the wallet in local storage * * @param walletKey string */ static _setWallet(walletKey) { return __awaiter(this, void 0, void 0, function* () { const details = this.getWalletDetailsFromStorage(); if (!details) { window.localStorage.setItem(this.localStorageKey, JSON.stringify({ name: this.wallet.name, icon: this.wallet.icon, apiVersion: this.wallet.apiVersion, key: walletKey })); } }); } /** * * Helper function to add supported wallets. * If none are added, all wallets are supported * * @param wallets string[], e.g. ['nami', 'eternal'] (window.cardano[key]) * */ static addSupportedWallets(wallets) { this.supportedWalletNames = [...this.supportedWalletNames, ...wallets]; } static setAdditionalWalletData(data) { const item = window.localStorage.getItem(this.localStorageKey); if (!item) { throw new Error(`No data saved to local storage. Missing ${this.localStorageKey}`); } const jsonItem = JSON.parse(item); const updatedItem = Object.assign(Object.assign({}, jsonItem), data); window.localStorage.setItem(this.localStorageKey, JSON.stringify(updatedItem)); } // 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_1.WalletError.WindowNotDefined); } if (!window.cardano) { throw new Error(WalletError_1.WalletError.NoWalletsFound); } if (!window.cardano[walletKey]) { throw new Error(WalletError_1.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 connect(walletKey) { return __awaiter(this, void 0, void 0, function* () { if (this.supportedWalletNames.length > 0) this.validateSupportedWallet(walletKey); this.validateWallet(walletKey); const wallet = window.cardano[walletKey]; yield this._enableWallet(wallet); this.wallet = wallet; this._setWallet(walletKey); return this.wallet; }); } static getWalletDetailsFromStorage() { const wallet = window.localStorage.getItem(this.localStorageKey); if (!wallet) { return null; } const walletDetails = JSON.parse(wallet); if (!walletDetails) { return null; } return walletDetails; } // Custom Methods /** * * Uses the CIP-30 getNetworkId to check if wallet is in mainnet or testnet * * @returns boolean */ static isMainnet() { return __awaiter(this, void 0, void 0, function* () { const networkId = yield this._enabledWallet.getNetworkId(); return networkId === 1; }); } /** * * Fetches the wallet balance and converts it to ADA * * @returns number */ static getAdaBalance() { return __awaiter(this, void 0, void 0, function* () { const balanceHex = yield this.getBalance(); const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); const balance = serializationLib.Value.from_bytes(buffer_1.Buffer.from(balanceHex, 'hex')).coin().to_str(); return parseInt(balance) / 1000000; }); } /** * * Used to verify that a wallet has the minimum necessary funds * * @param minimumBalance number */ static verifyBalance(minimumBalance) { return __awaiter(this, void 0, void 0, function* () { if (minimumBalance <= 0) { throw new Error(WalletError_1.WalletError.MinimumBalanceIsZero); } const adaBalance = yield this.getAdaBalance(); if (adaBalance <= minimumBalance) { throw new Error(WalletError_1.WalletError.InsufficientBalance); } }); } /** * * Used to verify a wallet is actively staked * * @param { rewardAddress: string } * @returns void if staked, throws NotDelegated error if not * */ static verifyStaking({ rewardAddress }) { return __awaiter(this, void 0, void 0, function* () { const result = yield blockfrost_1.Blockfrost.getAccountsRegistrations(rewardAddress); if (result.error) throw new Error(WalletError_1.WalletError.NotDelegated); if (!result.data) throw new Error(WalletError_1.WalletError.NotDelegated); const { data: [currentRegistration] } = result; if (!currentRegistration) { throw new Error(WalletError_1.WalletError.NotDelegated); } }); } /** * * Gets bech32 addresses from UTxOs * * @returns string[] */ static getUtxoBech32Addresses() { return __awaiter(this, void 0, void 0, function* () { const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); const rawUtxos = yield this.getUtxos(); const bech32Address = rawUtxos.map((rawUtxo) => { const utxo = serializationLib.TransactionUnspentOutput.from_bytes(buffer_1.Buffer.from(rawUtxo, 'hex')); const output = utxo.output(); return output.address().to_bech32(); }); return bech32Address; }); } /** * * Uses the serialization library to convert hex encoded utxos to human readable values * * @param rawUtxos Raw utxos from getUtxos() * @returns Utxo[] */ static buildUtxos(rawUtxos) { return __awaiter(this, void 0, void 0, function* () { const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); const utxoDetails = []; for (const rawUtxo of rawUtxos) { const utxo = serializationLib.TransactionUnspentOutput.from_bytes(buffer_1.Buffer.from(rawUtxo, 'hex')); const input = utxo.input(); const txIdBytes = input.transaction_id().to_bytes(); const txId = buffer_1.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(); const allAssets = []; 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(); const policyHex = buffer_1.Buffer.from(policyBytes, 'utf8').toString('hex'); const assets = multiasset.get(policy); const assetNames = assets === null || assets === void 0 ? void 0 : assets.keys(); if (assetNames) { const assetLenth = assetNames.len(); for (let j = 0; j < assetLenth; j++) { const assetName = assetNames.get(j); const assetNameBytes = assetName.name(); const assetNameString = buffer_1.Buffer.from(assetNameBytes, 'utf8').toString(); const assetNameHex = buffer_1.Buffer.from(assetNameBytes, 'utf8').toString('hex'); allAssets.push({ policyId: policyHex, name: assetNameString, hex: assetNameHex }); } } } } utxoDetails.push({ txId, txIndx, lovelaceAmount, assets: allAssets }); } return utxoDetails; }); } static buildTransaction({ paymentDetails, feeDetails }) { return __awaiter(this, void 0, void 0, function* () { try { //const serializationLib = lib; const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); const protocolParams = { linearFee: { minFeeA: '44', minFeeB: '155381' }, minUtxo: '34482', poolDeposit: '500000000', keyDeposit: '2000000', maxValSize: 5000, maxTxSize: 16384, priceMem: 0.0577, priceStep: 0.0000721, coinsPerUtxoWord: '34482' }; const txBuilder = serializationLib.TransactionBuilder.new(serializationLib.TransactionBuilderConfigBuilder.new() .fee_algo(serializationLib.LinearFee.new(serializationLib.BigNum.from_str(protocolParams.linearFee.minFeeA), serializationLib.BigNum.from_str(protocolParams.linearFee.minFeeB))) .pool_deposit(serializationLib.BigNum.from_str(protocolParams.poolDeposit)) .key_deposit(serializationLib.BigNum.from_str(protocolParams.keyDeposit)) .coins_per_utxo_word(serializationLib.BigNum.from_str(protocolParams.coinsPerUtxoWord)) .max_value_size(protocolParams.maxValSize) .max_tx_size(protocolParams.maxTxSize) .prefer_pure_change(true) .build()); const { address, lovelaceAmount, changeAddress } = paymentDetails; const shelleyOutputAddress = serializationLib.Address.from_bech32(address); txBuilder.add_output(serializationLib.TransactionOutput.new(shelleyOutputAddress, serializationLib.Value.new(serializationLib.BigNum.from_str(lovelaceAmount)))); if (feeDetails) { const feeOutputAddress = serializationLib.Address.from_bech32(feeDetails.address); txBuilder.add_output(serializationLib.TransactionOutput.new(feeOutputAddress, serializationLib.Value.new(serializationLib.BigNum.from_str(feeDetails.lovelaceAmount)))); } const rawUtxos = yield this.getUtxos(); const txUnspentOutputs = rawUtxos.reduce((acc, utxo) => { const fromBytes = serializationLib.TransactionUnspentOutput.from_bytes(buffer_1.Buffer.from(utxo, 'hex')); acc.add(fromBytes); return acc; }, serializationLib.TransactionUnspentOutputs.new()); txBuilder.add_inputs_from(txUnspentOutputs, 0); const shelleyChangeAddress = serializationLib.Address.from_bech32(changeAddress); txBuilder.add_change_if_needed(shelleyChangeAddress); const builtTransaction = txBuilder.build(); const txHash = buffer_1.Buffer.from(serializationLib.hash_transaction(builtTransaction).to_bytes()).toString('hex'); const transaction = serializationLib.Transaction.new(builtTransaction, serializationLib.TransactionWitnessSet.new()); return { txHash, tx: buffer_1.Buffer.from(transaction.to_bytes()).toString('hex') }; } catch (error) { console.log(error); throw new Error(error); } }); } static signTransaction(tx) { return __awaiter(this, void 0, void 0, function* () { const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); let txVkeyWitnesses = yield this.signTx(tx, true); txVkeyWitnesses = serializationLib.TransactionWitnessSet.from_bytes(buffer_1.Buffer.from(txVkeyWitnesses, 'hex')); const transactionWitnessSet = serializationLib.TransactionWitnessSet.new(); transactionWitnessSet.set_vkeys(txVkeyWitnesses.vkeys()); const txBody = serializationLib.Transaction.from_bytes(buffer_1.Buffer.from(tx, 'hex')); const signedTx = serializationLib.Transaction.new(txBody.body(), transactionWitnessSet); const signedTxHex = buffer_1.Buffer.from(signedTx.to_bytes()).toString('hex'); return signedTxHex; }); } static submitSignedTransaction(signedTxHex) { return __awaiter(this, void 0, void 0, function* () { const submittedTxHash = yield this.submitTx(signedTxHex); return submittedTxHash; }); } static signAndSubmitTransaction(tx) { return __awaiter(this, void 0, void 0, function* () { const signedTxHex = yield this.signTransaction(tx); const submittedTxHash = yield this.submitTx(signedTxHex); return submittedTxHash; }); } } exports.CardanoWallets = CardanoWallets; _a = CardanoWallets; CardanoWallets.localStorageKey = 'cardanoWallet'; CardanoWallets.supportedWalletNames = []; /** * * Uses CIP-30 'enable' function to enable the wallet * * @param wallet Wallet to enable * @returns */ CardanoWallets._enableWallet = (wallet) => __awaiter(void 0, void 0, void 0, function* () { _a._enabledWallet = yield wallet.enable(); }); CardanoWallets.disableWallet = () => __awaiter(void 0, void 0, void 0, function* () { localStorage.removeItem(_a.localStorageKey); }); // CIP-30 methods /** * * CIP-30 method to check if wallet is enabled * https://cips.cardano.org/cips/cip30/#cardanowalletnameisenabledpromisebool * * @returns CIP-30 Wallet */ CardanoWallets.isWalletEnabled = () => __awaiter(void 0, void 0, void 0, function* () { return _a.wallet.isEnabled(); }); /** * * CIP-30 method to get the balance of the wallet * https://cips.cardano.org/cips/cip30/#apigetbalancepromisecborvalue * * @returns balanceHex */ CardanoWallets.getBalance = () => __awaiter(void 0, void 0, void 0, function* () { const balanceHex = yield _a._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) */ CardanoWallets.getNetworkId = () => __awaiter(void 0, void 0, void 0, function* () { const networkId = yield _a._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 */ CardanoWallets.getUtxos = (amount, paginate) => __awaiter(void 0, void 0, void 0, function* () { const rawUtxos = yield _a._enabledWallet.getUtxos(amount, paginate); return rawUtxos; }); /** * * CIP-30 method to get wallet collateral * https://cips.cardano.org/cips/cip30/#apigetcollateralparamsamountcborcoinpromisetransactionunspentoutputnull * * @returns list of Utxos */ CardanoWallets.getCollateral = () => __awaiter(void 0, void 0, void 0, function* () { const collateral = yield _a._enabledWallet.getCollateral(); return collateral; }); /** * * CIP-30 method to get unused addresses * https://cips.cardano.org/cips/cip30/#apigetunusedaddressespromiseaddress * * @returns list of unused addresses */ CardanoWallets.getUnusedAddresses = () => __awaiter(void 0, void 0, void 0, function* () { const unusedAddresses = yield _a._enabledWallet.getUnusedAddresses(); return unusedAddresses; }); /** * * CIP-30 method to get a change address * https://cips.cardano.org/cips/cip30/#apigetchangeaddresspromiseaddress * * @returns change address */ CardanoWallets.getChangeAddress = () => __awaiter(void 0, void 0, void 0, function* () { const changeAddress = yield _a._enabledWallet.getChangeAddress(); const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); const bech32Address = serializationLib.Address.from_bytes(buffer_1.Buffer.from(changeAddress, 'hex')).to_bech32(); return bech32Address; }); /** * * CIP-30 method to get a reward address * https://cips.cardano.org/cips/cip30/#apigetrewardaddressespromiseaddress * * @returns a reward address */ CardanoWallets.getRewardAddresses = () => __awaiter(void 0, void 0, void 0, function* () { const rewardAddresses = yield _a._enabledWallet.getRewardAddresses(); const serializationLib = yield (0, serialize_1.loadCardanoWasm)(); const bech32RewardAddress = rewardAddresses.map((addr) => serializationLib.Address.from_bytes(buffer_1.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 */ CardanoWallets.signTx = (tx, partialSign = false) => __awaiter(void 0, void 0, void 0, function* () { const result = yield _a._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 */ CardanoWallets.submitTx = (tx) => __awaiter(void 0, void 0, void 0, function* () { const transactionId = yield _a._enabledWallet.submitTx(tx); return transactionId; });