@koralabs/cardano-wallets
Version:
Library for connecting cardano wallets in the browser using CIP-30
477 lines (476 loc) • 19.8 kB
JavaScript
"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;
});