├── .nvmrc ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── src ├── components │ ├── wallet │ │ ├── wallet-details │ │ │ ├── WalletSend.vue │ │ │ ├── WalletReceive.vue │ │ │ ├── WalletTransactions.vue │ │ │ └── WalletSyncing.vue │ │ ├── NoWallet.vue │ │ ├── AddWallet.vue │ │ └── WalletDetails.vue │ ├── Loader.vue │ ├── MainView.vue │ ├── StakingPage.vue │ └── WalletPage.vue ├── assets │ ├── logo.png │ └── logo.svg ├── lang │ ├── locals │ │ ├── en_US.json │ │ └── es_ES.json │ └── index.js ├── store │ ├── wallet │ │ ├── mutations.js │ │ ├── state.js │ │ ├── index.js │ │ ├── types.js │ │ ├── getters.js │ │ └── actions.js │ └── index.js ├── main.js ├── core │ ├── common.js │ ├── cardano-addresses.js │ ├── dandelion.js │ └── cardano-cli.js ├── App.vue ├── plugins │ └── vuetify.js ├── cardano.js ├── background.js └── services │ └── wallet.service.js ├── public ├── favicon.ico └── index.html ├── babel.config.js ├── .gitignore ├── .build └── binaries.sh ├── vue.config.js ├── README.md ├── baids └── 10-build-functions ├── package.json ├── CODE_OF_CONDUCT.md ├── .github └── workflows │ └── draft-pr-releases.yaml └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | 123,125 -------------------------------------------------------------------------------- /src/components/wallet/wallet-details/WalletSend.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/wallet/wallet-details/WalletReceive.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/wallet/wallet-details/WalletTransactions.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOnChain/lift-wallet/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOnChain/lift-wallet/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/lang/locals/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "notice": { 3 | "msg": "You have message" 4 | }, 5 | "wallet": { 6 | "add_wallet": "add wallet", 7 | "add": "add" 8 | }, 9 | "main": { 10 | }, 11 | "language": "English" 12 | } -------------------------------------------------------------------------------- /src/store/wallet/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './types'; 2 | export default { 3 | [types.SET_MNEMONIC]: (state, mnemonic) => { 4 | state.mnemonic = mnemonic; 5 | }, 6 | [types.SET_WALLET]: (state, wallet) => { 7 | state.wallet = wallet; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/store/wallet/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mnemonic: '', 3 | wallet:NaN, 4 | wordsNumbersAllowed:[9, 12, 15, 18, 21, 24], 5 | network:NaN, 6 | walletName:NaN, 7 | assetName:NaN, 8 | tokenAmount:NaN, 9 | passphrase:NaN, 10 | metadata:NaN 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /src/lang/locals/es_ES.json: -------------------------------------------------------------------------------- 1 | { 2 | "notice": { 3 | "msg": "Tienes un mensaje" 4 | }, 5 | "wallet": { 6 | "add_wallet": "agregar billetera", 7 | "add": "agregar" 8 | }, 9 | "main": { 10 | }, 11 | "language": "Español" 12 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import vuetify from './plugins/vuetify'; 4 | import store from "./store"; 5 | import i18n from './lang'; 6 | 7 | Vue.config.productionTip = false; 8 | console.log("store ",store); 9 | new Vue({ 10 | vuetify, 11 | i18n, 12 | store, 13 | render: h => h(App) 14 | }).$mount('#app'); 15 | -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueI18n from 'vue-i18n'; 3 | import en from '../lang/locals/en_US.json'; 4 | import es from '../lang/locals/es_ES.json'; 5 | 6 | Vue.use(VueI18n); 7 | 8 | export default new VueI18n({ 9 | locale: 'en', 10 | messages: { 11 | en: { 12 | lang: en 13 | }, 14 | es: { 15 | lang: es 16 | } 17 | } 18 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .next 4 | app 5 | dist 6 | cardano/configs 7 | cardano/darwin 8 | cardano/macos64 9 | cardano/win32 10 | cardano/win64 11 | cardano/linux64 12 | cardano/db-test 13 | cardano/wallets-test 14 | cardano/db 15 | cardano/wallets 16 | cardano/assets 17 | cardano/socket 18 | cardano/sockets 19 | cardano/lift-db 20 | #Electron-builder output 21 | /dist_electron 22 | .env 23 | .env.* -------------------------------------------------------------------------------- /src/store/wallet/index.js: -------------------------------------------------------------------------------- 1 | import state from './state'; 2 | import getters from './getters'; 3 | import mutations from './mutations'; 4 | import actions from './actions'; 5 | 6 | //req:mint-asset 7 | //ipcRenderer.on('res:mint-asset', callback); 8 | //args.network, args.walletName, args.assetName, args.tokenAmount, args.passphrase, args.metadata 9 | export default { 10 | namespaced: true, 11 | state, 12 | getters, 13 | mutations, 14 | actions 15 | }; 16 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | // we first import the module 4 | import wallet from './wallet'; 5 | 6 | 7 | Vue.use(Vuex); 8 | export default function (/* { ssrContext } */) { 9 | const Store = new Vuex.Store({ 10 | modules: { 11 | wallet 12 | }, 13 | 14 | // enable strict mode (adds overhead!) 15 | // for dev mode only 16 | strict: process.env.DEV 17 | }); 18 | 19 | return Store; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/wallet/NoWallet.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "dev", 7 | "isBackground": true, 8 | "problemMatcher": { 9 | "owner": "custom", 10 | "pattern": { 11 | "regexp": "" 12 | }, 13 | "background": { 14 | "beginsPattern": "started server", 15 | "endsPattern": "Debugger listening on" 16 | } 17 | }, 18 | "label": "dev" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /src/components/Loader.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /src/components/MainView.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/wallet/wallet-details/WalletSyncing.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /src/store/wallet/types.js: -------------------------------------------------------------------------------- 1 | // Namespace 2 | export const NAMESPACE = 'wallet/'; 3 | 4 | // Getters 5 | export const MNEMONIC= 'MNEMONIC'; 6 | export const WALLET= 'WALLET'; 7 | export const WORDS_NUMBER_ALLOWED= 'WORDS_NUMBER_ALLOWED'; 8 | 9 | 10 | // Mutations 11 | export const SET_MNEMONIC = 'SET_MNEMONIC'; 12 | export const SET_WALLET = 'SET_WALLET'; 13 | 14 | export const NETWORK = 'NETWORK'; 15 | export const WALLET_NAME = 'WALLET_NAME'; 16 | export const ASSET_NAME = 'ASSET_NAME'; 17 | export const TOKEN_AMOUNT = 'TOKEN_AMOUNT'; 18 | export const PASSPHRASE = 'PASSPHRASE'; 19 | export const METADATA = 'METADATA'; 20 | 21 | 22 | // Actions 23 | export const SET_UP_WALLET = 'SET_UP_WALLET'; 24 | export const ADD_WALLET = 'ADD_WALLET'; 25 | export const GET_NEW_MNEMONIC = 'GET_MNEMONIC'; 26 | -------------------------------------------------------------------------------- /src/store/wallet/getters.js: -------------------------------------------------------------------------------- 1 | import * as types from './types'; 2 | export default { 3 | [types.MNEMONIC]: (state) => { 4 | return state.mnemonic; 5 | }, 6 | [types.WALLET]: (state) => { 7 | return state.wallet; 8 | }, 9 | [types.WORDS_NUMBER_ALLOWED]: (state) => { 10 | return state.wordsNumbersAllowed; 11 | }, 12 | [types.NETWORK]: (state) => { 13 | return state.network; 14 | }, 15 | [types.WALLET_NAME]: (state) => { 16 | return state.walletName; 17 | }, 18 | [types.ASSET_NAME]: (state) => { 19 | return state.assetName; 20 | }, 21 | [types.TOKEN_AMOUNT]: (state) => { 22 | return state.tokenAmount; 23 | }, 24 | [types.PASSPHRASE]: (state) => { 25 | return state.passphrase; 26 | }, 27 | [types.METADATA]: (state) => { 28 | return state.metadata; 29 | } 30 | }; 31 | 32 | 33 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.build/binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | downloadBinaries() { 6 | mkdir -p ./cardano/$2 7 | if [ "$2" == 'win64' ]; 8 | then 9 | wget -q -O .build/tmp.zip "https://github.com/input-output-hk/cardano-wallet/releases/download/$1/cardano-wallet-$1-$2.zip" && unzip -j .build/tmp.zip -d ./cardano/$2 && rm .build/tmp.zip 10 | else 11 | wget -qO- "https://github.com/input-output-hk/cardano-wallet/releases/download/$1/cardano-wallet-$1-$2.tar.gz" | tar xvz - --strip-components 1 -C ./cardano/$2 12 | 13 | fi 14 | } 15 | 16 | if [ -z $2 ]; 17 | then 18 | CARDANO_WALLET_TAG=$(git ls-remote --tags https://github.com/input-output-hk/cardano-wallet.git | awk '{print $2}' | cut -d '/' -f 3 | cut -d '^' -f 1 | sort -b -t . -k 1,1nr -k 2,2nr -k 3,3r -k 4,4r -k 5,5r | uniq | tail -1); 19 | else 20 | CARDANO_WALLET_TAG=$2 21 | fi 22 | 23 | downloadBinaries $CARDANO_WALLET_TAG $1 -------------------------------------------------------------------------------- /src/components/StakingPage.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | "transpileDependencies": [ 5 | "vuetify" 6 | ], 7 | pluginOptions: { 8 | electronBuilder: { 9 | nodeIntegration: true, 10 | builderOptions: { 11 | appId: "com.electron.lift-wallet", 12 | productName: "LIFT Wallet", 13 | nsis: { 14 | oneClick: false, 15 | perMachine: true, 16 | allowToChangeInstallationDirectory: true 17 | }, 18 | mac: { 19 | target: "dmg", 20 | extraFiles: [ 21 | "cardano/macos*/**/*", 22 | "cardano/configs/**/*" 23 | ] 24 | }, 25 | linux: { 26 | target: "AppImage", 27 | extraFiles: [ 28 | "cardano/linux*/**/*", 29 | "cardano/configs/**/*" 30 | ] 31 | }, 32 | win: { 33 | target: "nsis", 34 | extraFiles: [ 35 | "cardano/win*/**/*", 36 | "cardano/configs/**/*" 37 | ] 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Nextron: Main", 9 | "type": "node", 10 | "request": "attach", 11 | "protocol": "inspector", 12 | "port": 9292, 13 | "skipFiles": ["/**"], 14 | "sourceMapPathOverrides": { 15 | "webpack:///./~/*": "${workspaceFolder}/node_modules/*", 16 | "webpack:///./*": "${workspaceFolder}/*", 17 | "webpack:///*": "*" 18 | } 19 | }, 20 | { 21 | "name": "Nextron: Renderer", 22 | "type": "chrome", 23 | "request": "attach", 24 | "port": 5858, 25 | "timeout": 10000, 26 | "urlFilter": "http://localhost:*", 27 | "webRoot": "${workspaceFolder}/app", 28 | "sourceMapPathOverrides": { 29 | "webpack:///./src/*": "${webRoot}/*" 30 | } 31 | } 32 | ], 33 | "compounds": [ 34 | { 35 | "name": "Nextron: All", 36 | "preLaunchTask": "dev", 37 | "configurations": ["Nextron: Main", "Nextron: Renderer"] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/store/wallet/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from "./types.js"; 2 | const { ipcRenderer } = require("electron"); 3 | const actions = { 4 | async [types.GET_NEW_MNEMONIC]({ state }, { wordsNumber }) { 5 | var wordsAmmountToBeGenerated = state.wordsNumbersAllowed.find( 6 | (x) => x === wordsNumber 7 | ); 8 | if (wordsAmmountToBeGenerated == null) throw "not allowed lenght"; 9 | ipcRenderer.send("req:generate-recovery-phrase"); 10 | }, 11 | 12 | [types.SET_UP_WALLET]({ commit }) { 13 | console.log("set up wallet"); 14 | ipcRenderer.on("res:generate-recovery-phrase", (_, args) => { 15 | console.log("phrase", args); 16 | if (args.isSuccessful) { 17 | console.log("generate recovery phrase", args.data); 18 | commit(types.SET_MNEMONIC, args.data); 19 | } else { 20 | console.log(args.data); 21 | } 22 | }); 23 | ipcRenderer.on("res:add-wallet", (_, args) => { 24 | if (args.isSuccessful) { 25 | console.log("adding new wallet", args.data); 26 | commit(types.SET_WALLET, args.data); 27 | } else { 28 | console.log("adding wallet error"); 29 | } 30 | }); 31 | }, 32 | async [types.ADD_WALLET]({ state }, { walletForm }) { 33 | console.log(state); 34 | console.log('add a new wallet'); 35 | console.log('wallet form', walletForm); 36 | ipcRenderer.send('req:add-wallet', walletForm); 37 | } 38 | }; 39 | 40 | export default actions; 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # LIFT Wallet 4 | [![GitHub license](https://img.shields.io/github/license/nothingalike/lift-wallet)](https://github.com/nothingalike/lift-wallet/blob/master/LICENSE) 5 | 6 | A Cardano Cryptocurrency Lite wallet. It ultilizes `cardano-cli` and `cardano-address`. 7 | 8 | 9 | ## Still in Development. 10 | Proceed with caution. 11 | 12 | 13 | ## Prerequisites 14 | 15 | - git 16 | - nodejs/nvm 17 | - bash 18 | - wget 19 | 20 | We recommend using these methods to fulfill prerequisites: 21 | 22 | - Windows users, try some [choco](https://chocolatey.org/): 23 | ``` 24 | choco install git nvm bash wget 25 | ``` 26 | - MacOS users, try some [brew](https://brew.sh/): 27 | ``` 28 | brew install git nvm bash wget 29 | ``` 30 | - Linux: feel free :) 31 | 32 | ## Initial setup 33 | 34 | 1. Clone the project in your computer 35 | ``` bash 36 | git clone https://github.com/nothingalike/lift-wallet.git 37 | ``` 38 | 2. Install `node` dependencies 39 | ``` 40 | # install required nodejs version from .nvmrc 41 | nvm install 42 | nvm use 43 | # install nodejs modules/dependencies 44 | npm install 45 | ``` 46 | 4. Download latest `cardano` binaries for your platform 47 | ``` bash 48 | npm run binaries:macos 49 | npm run binaries:linux 50 | npm run binaries:windows 51 | ``` 52 | 53 | ## Run 54 | 55 | ``` 56 | npm run electron:serve 57 | ``` 58 | 59 | A clean instance of `lift-wallet` should pop up! 60 | 61 | ![alt text](https://user-images.githubusercontent.com/35784914/105702296-5963c100-5eea-11eb-9cb3-83ec46753379.PNG) 62 | -------------------------------------------------------------------------------- /baids/10-build-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function cardano-wallet-get-latest-tag() { 4 | 5 | GIT_REPO="https://github.com/input-output-hk/cardano-wallet.git" 6 | git ls-remote --tags ${GIT_REPO} | awk '{print $NF}' | tail -n1 | sed 's|refs/tags/\(v....-..-..\).*|\1|g' 7 | 8 | } 9 | 10 | function cardano-wallet-download-binaries() { 11 | 12 | PLATFORM="$1" 13 | TAG="$2" && test -z "${TAG}" && TAG=$(cardano-wallet-get-latest-tag) 14 | REQUIRED_BINARIES="cardano-address cardano-cli bech32" 15 | 16 | TMP_DIR="/tmp/cardano-wallet" && mkdir -p "${TMP_DIR}" 17 | PLATFORM_DIR="cardano/${PLATFORM}" && mkdir -p "${PLATFORM_DIR}" 18 | 19 | case "${PLATFORM}" in 20 | win*) 21 | RELEASE_URL="https://github.com/input-output-hk/cardano-wallet/releases/download/${TAG}/cardano-wallet-${TAG}-${PLATFORM}.zip" 22 | wget -q -O "${TMP_DIR}/cardano-wallet-${TAG}-${PLATFORM}.zip" "${RELEASE_URL}" 23 | unzip -j "${TMP_DIR}/cardano-wallet-${TAG}-${PLATFORM}.zip" -d "${TMP_DIR}" 24 | rm -f "${TMP_DIR}/cardano-wallet-${TAG}-${PLATFORM}.zip" 25 | for bin in ${REQUIRED_BINARIES} 26 | do 27 | mv "${TMP_DIR}/${bin}.exe" "${PLATFORM_DIR}/${bin}.exe" 28 | done 29 | for lib in ${TMP_DIR}/*dll 30 | do 31 | mv "${lib}" "${PLATFORM_DIR}/" 32 | done 33 | ;; 34 | *) 35 | RELEASE_URL="https://github.com/input-output-hk/cardano-wallet/releases/download/${TAG}/cardano-wallet-${TAG}-${PLATFORM}.tar.gz" 36 | wget -qO- "${RELEASE_URL}" | tar -xvz --strip-components 1 -C "${TMP_DIR}" 37 | for bin in ${REQUIRED_BINARIES} 38 | do 39 | mv "${TMP_DIR}/${bin}" "${PLATFORM_DIR}/" 40 | done 41 | for lib in ${TMP_DIR}/*dylib 42 | do 43 | mv "${lib}" "${PLATFORM_DIR}/" || true 44 | done 45 | ;; 46 | esac 47 | 48 | 49 | 50 | rm -rf "${TMP_DIR}" 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/core/common.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import util from 'util'; 3 | import crypto from 'crypto'; 4 | import { exec } from 'child_process' 5 | 6 | const cmd = util.promisify(exec); 7 | 8 | export async function cli(statement) { 9 | return await cmd(statement) 10 | } 11 | 12 | export function encrypt(prvFile, pubFile, password) { 13 | try { 14 | const algorithm = "aes-192-cbc"; 15 | const prvData = fs.readFileSync(prvFile); 16 | const pubData = fs.readFileSync(pubFile); 17 | 18 | const key = crypto.scryptSync(password, pubData.slice(16, 32), 24); //create key 19 | 20 | const cipher = crypto.createCipheriv(algorithm, key, pubData.slice(0, 16)); 21 | var encrypted = cipher.update(prvData.toString(), 'utf8', 'hex') + cipher.final('hex'); // encrypted text 22 | fs.writeFileSync(prvFile, encrypted); 23 | } catch (exception) { 24 | throw exception.message; 25 | } 26 | } 27 | 28 | export function decrypt(prvFile, pubFile, password) { 29 | try { 30 | const algorithm = "aes-192-cbc"; 31 | const prvData = fs.readFileSync(prvFile); 32 | const pubData = fs.readFileSync(pubFile); 33 | 34 | const key = crypto.scryptSync(password, pubData.slice(16, 32), 24); //create key 35 | 36 | const decipher = crypto.createDecipheriv(algorithm, key, pubData.slice(0, 16)); 37 | const decrypted = decipher.update(prvData.toString(), 'hex', 'utf8') + decipher.final('utf8'); //deciphered text 38 | return decrypted; 39 | } catch (exception) { 40 | throw exception.message; 41 | } 42 | } 43 | 44 | export function hex_to_ascii(str1) 45 | { 46 | str1 = str1.replace('\\x', ''); 47 | var hex = str1.toString(); 48 | var str = ''; 49 | for (var n = 0; n < hex.length; n += 2) { 50 | str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); 51 | } 52 | return str; 53 | } -------------------------------------------------------------------------------- /src/core/cardano-addresses.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const isDevelopment = process.env.NODE_ENV !== 'production' 4 | 5 | export const cardanoPath = isDevelopment 6 | ? path.resolve(__dirname, '..', 'cardano') 7 | : path.resolve(__dirname, '..', '..', 'cardano') ; 8 | 9 | export const cardanoPlatformPath = process.platform === 'darwin' ? 'macos64': 10 | process.platform === 'win32' ? 'win64': 11 | process.platform === 'linux' ? 'linux64': 12 | process.platform; 13 | 14 | export function getMnemonicCmd(size){ 15 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 16 | return `"${addressCli}" recovery-phrase generate --size ${size}`; 17 | } 18 | 19 | export function getRootCmd(mnemonic) { 20 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 21 | return `echo ${mnemonic} | "${addressCli}" key from-recovery-phrase Shelley`; 22 | } 23 | 24 | export function getChildCmd(stdIn, derivation) { 25 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 26 | return `echo ${stdIn}| "${addressCli}" key child ${derivation}`; 27 | } 28 | 29 | export function getPublicCmd(prvKey) { 30 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 31 | return `echo ${prvKey}| "${addressCli}" key public --with-chain-code`; 32 | } 33 | 34 | export function getBaseAddrCmd(pubKey, network){ 35 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 36 | return `echo ${pubKey}| "${addressCli}" address payment --network-tag ${network}`; 37 | } 38 | 39 | export function getPaymentAddrCmd(baseAddr, stakePub){ 40 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 41 | return `echo ${baseAddr}| "${addressCli}" address delegation "${stakePub}"`; 42 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lift-wallet", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve eslint src", 7 | "build": "vue-cli-service build eslint src", 8 | "lint": "vue-cli-service lint", 9 | "electron:build": "vue-cli-service electron:build", 10 | "electron:serve": "vue-cli-service electron:serve", 11 | "postinstall": "electron-builder install-app-deps", 12 | "postuninstall": "electron-builder install-app-deps", 13 | "binaries:macos":"bash -ec 'source baids/[0-9]*; cardano-wallet-download-binaries macos64'", 14 | "binaries:windows":"bash -ec 'source baids/[0-9]*; cardano-wallet-download-binaries win64'", 15 | "binaries:linux":"bash -ec 'source baids/[0-9]*; cardano-wallet-download-binaries linux64'" 16 | }, 17 | "main": "background.js", 18 | "dependencies": { 19 | "axios": "^0.21.0", 20 | "cardano-crypto.js": "^6.0.0", 21 | "core-js": "^3.6.5", 22 | "dayjs": "^1.10.3", 23 | "vue": "^2.6.11", 24 | "vue-i18n": "^8.22.4", 25 | "vuelidate": "^0.7.6", 26 | "vuetify": "^2.2.11", 27 | "vuex": "^3.6.2" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "~4.5.0", 31 | "@vue/cli-plugin-eslint": "~4.5.0", 32 | "@vue/cli-service": "~4.5.0", 33 | "babel-eslint": "^10.1.0", 34 | "copy-webpack-plugin": "^6.3.2", 35 | "electron": "^9.0.0", 36 | "electron-devtools-installer": "^3.1.0", 37 | "eslint": "^6.7.2", 38 | "eslint-plugin-vue": "^6.2.2", 39 | "sass": "^1.19.0", 40 | "sass-loader": "^8.0.0", 41 | "vue-cli-plugin-electron-builder": "~2.0.0-rc.5", 42 | "vue-cli-plugin-vuetify": "~2.0.7", 43 | "vue-template-compiler": "^2.6.11", 44 | "vuetify-loader": "^1.3.0" 45 | }, 46 | "eslintConfig": { 47 | "root": true, 48 | "env": { 49 | "node": true 50 | }, 51 | "extends": [ 52 | "plugin:vue/essential", 53 | "eslint:recommended" 54 | ], 55 | "parserOptions": { 56 | "parser": "babel-eslint" 57 | }, 58 | "rules": { 59 | "semi": 2, 60 | "indent": ["error", 2] 61 | } 62 | }, 63 | "browserslist": [ 64 | "> 1%", 65 | "last 2 versions", 66 | "not dead" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 93 | -------------------------------------------------------------------------------- /src/core/dandelion.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export async function getUtxos(network, addresses) { 4 | var utxos = `{ utxos( order_by: { value: desc } where: { address: { _in: ${getGraphqlList(addresses)} }} ) { address index txHash value tokens { policyId assetName quantity } } }` 5 | var utxosResult = await axios.post(getGraphqlUrl(network), { query: utxos }); 6 | return utxosResult.data.data.utxos; 7 | } 8 | 9 | export async function getProtocolParams(network) { 10 | var protocolParamsQuery = "{ genesis { shelley { protocolParams { a0 decentralisationParam eMax extraEntropy keyDeposit maxBlockBodySize maxBlockHeaderSize maxTxSize minFeeA minFeeB minPoolCost minUTxOValue nOpt poolDeposit protocolVersion rho tau } } } }" 11 | var protocolParamsResult = await axios.post(getGraphqlUrl(network), { query: protocolParamsQuery }); 12 | return protocolParamsResult.data.data.genesis.shelley.protocolParams; 13 | } 14 | 15 | export async function getCurrentSlotNo(network) { 16 | //gathering data for constructing a transaction 17 | var tipQuery = "{ cardano { tip { slotNo } } }"; 18 | var tipResult = await axios.post(getGraphqlUrl(network), { query: tipQuery }); 19 | return tipResult.data.data.cardano.tip.slotNo; 20 | } 21 | 22 | export async function submitTransaction(network, signedTxBinary) { 23 | 24 | var transactionQuery = "mutation submitTransaction( $transaction: String! ) { submitTransaction(transaction: $transaction) { hash } }"; 25 | var transactionResult = await axios.post(getGraphqlUrl(network), { query: transactionQuery, variables: { transaction: signedTxBinary.cborHex } }); 26 | console.log(transactionResult.data.data.submitTransaction.hash); 27 | return transactionResult.data.data.submitTransaction.hash; 28 | } 29 | 30 | export async function getTransactionsByAddresses(network, addresses) { 31 | const payload = { 32 | data: { 33 | addresses: addresses 34 | } 35 | }; 36 | 37 | var sendResult = await axios( 38 | { 39 | method: 'post', 40 | url: `${getPostgrestApiUrl(network)}rpc/get_tx_history_for_addresses`, 41 | data: payload, 42 | headers: { 43 | "Content-Type": "application/json" 44 | } 45 | }); 46 | return sendResult.data; 47 | } 48 | 49 | export async function getTransactionsDetails(network, transactions) { 50 | //gathering data for constructing a transaction 51 | var transactionQuery = `{ transactions( where: { hash: { _in: ${getGraphqlList(transactions)} } } ) { hash fee deposit inputs { address value } outputs { address value } totalOutput includedAt } }` 52 | var transactionResult = await axios.post(getGraphqlUrl(network), { query: transactionQuery }); 53 | return transactionResult.data.data.transactions; 54 | } 55 | 56 | function getGraphqlUrl(network) { 57 | return `https://graphql-api.${network}.dandelion.link/`; 58 | } 59 | 60 | function getSubmitApiUrl(network) { 61 | return `https://submit-api.${network}.dandelion.link/`; 62 | } 63 | 64 | function getPostgrestApiUrl(network) { 65 | return `https://postgrest-api.${network}.dandelion.link/`; 66 | } 67 | 68 | function getOpenFaasUrl() { 69 | return `http://openfaas.dandelion.link/`; 70 | } 71 | 72 | function getGraphqlList(list) { 73 | let result = '['; 74 | list.forEach(a => { 75 | result += `"${a}",`; 76 | }) 77 | result += ']'; 78 | return result; 79 | } 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at liftstakepool@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify from "vuetify/lib"; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({ 7 | theme: { 8 | themes: { 9 | light: { 10 | primary: { 11 | base: "#55bb46", 12 | lighten5: "#e7ffcd", 13 | lighten4: "#c9ffb1", 14 | lighten3: "#acff96", 15 | lighten2: "#8ff47b", 16 | lighten1: "#72d760", 17 | darken1: "#369f2b", 18 | darken2: "#08850c", 19 | darken3: "#006b00", 20 | darken4: "#005100", 21 | }, 22 | secondary: { 23 | base: "#A0B9C1", 24 | lighten5: "#ffffff", 25 | lighten4: "#ffffff", 26 | lighten3: "#f4ffff", 27 | lighten2: "#d7f1ff", 28 | lighten1: "#bbd5e4", 29 | darken1: "#869ead", 30 | darken2: "#6c8492", 31 | darken3: "#536b78", 32 | darken4: "#3b5360", 33 | }, 34 | accent: { 35 | base: "#048ba8", 36 | lighten5: "#b6ffff", 37 | lighten4: "#98f9ff", 38 | lighten3: "#7addfc", 39 | lighten2: "#5cc1df", 40 | lighten1: "#3ba5c3", 41 | darken1: "#00718e", 42 | darken2: "#005974", 43 | darken3: "#00415b", 44 | darken4: "#002b44", 45 | }, 46 | error: { 47 | base: "#ef476f", 48 | lighten5: "#ffd9f5", 49 | lighten4: "#ffbcd9", 50 | lighten3: "#ff9fbd", 51 | lighten2: "#ff82a2", 52 | lighten1: "#ff6588", 53 | darken1: "#d02457", 54 | darken2: "#b10040", 55 | darken3: "#92002a", 56 | darken4: "#740017", 57 | }, 58 | info: { 59 | base: "#2196f3", 60 | lighten5: "#d4ffff", 61 | lighten4: "#b5ffff", 62 | lighten3: "#95e8ff", 63 | lighten2: "#75ccff", 64 | lighten1: "#51b0ff", 65 | darken1: "#007cd6", 66 | darken2: "#0064ba", 67 | darken3: "#004d9f", 68 | darken4: "#003784", 69 | }, 70 | success: { 71 | base: "#06d6a0", 72 | lighten5: "#caffff", 73 | lighten4: "#aaffff", 74 | lighten3: "#8bfff3", 75 | lighten2: "#6affd7", 76 | lighten1: "#45f3bb", 77 | darken1: "#00ba86", 78 | darken2: "#009e6c", 79 | darken3: "#008354", 80 | darken4: "#00693d", 81 | }, 82 | warning: { 83 | base: "#ffd166", 84 | lighten5: "#fffff1", 85 | lighten4: "#ffffd4", 86 | lighten3: "#ffffb8", 87 | lighten2: "#ffff9c", 88 | lighten1: "#ffed81", 89 | darken1: "#e1b64c", 90 | darken2: "#c39b31", 91 | darken3: "#a68111", 92 | darken4: "#896800", 93 | }, 94 | background: { 95 | base: "#ffffff", 96 | lighten5: "#ffffff", 97 | lighten4: "#ffffff", 98 | lighten3: "#ffffff", 99 | lighten2: "#ffffff", 100 | lighten1: "#ffffff", 101 | darken1: "#e2e2e2", 102 | darken2: "#c6c6c6", 103 | darken3: "#ababab", 104 | darken4: "#919191", 105 | }, 106 | surface: { 107 | base: "#f2f5f8", 108 | lighten5: "#ffffff", 109 | lighten4: "#ffffff", 110 | lighten3: "#ffffff", 111 | lighten2: "#ffffff", 112 | lighten1: "#ffffff", 113 | darken1: "#d6d9db", 114 | darken2: "#babdc0", 115 | darken3: "#9fa2a5", 116 | darken4: "#85888a", 117 | }, 118 | }, 119 | }, 120 | }, 121 | }); 122 | -------------------------------------------------------------------------------- /.github/workflows/draft-pr-releases.yaml: -------------------------------------------------------------------------------- 1 | name: draft-pr-releases 2 | on: 3 | pull_request: 4 | types: [opened, synchronize] 5 | 6 | jobs: 7 | build-artifacts: 8 | name: Build release artifacts 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: 13 | - ubuntu-latest 14 | - macos-latest 15 | - windows-latest 16 | node_version: 17 | - 14 18 | architecture: 19 | - x64 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Make git short-commit/pr-number available to different steps 23 | id: metadata 24 | shell: bash 25 | run: | 26 | echo "::set-output name=COMMIT::$(echo ${GITHUB_SHA} | head -c7)" 27 | echo "::set-output name=PR_NUMBER::$(echo ${GITHUB_REF} | awk -F/ '{print $3}')" 28 | - uses: actions/setup-node@v2 29 | with: 30 | node-version: ${{ matrix.node_version }} 31 | architecture: ${{ matrix.architecture }} 32 | - name: Build artifacts for ${{ matrix.os }}-${{ matrix.architecture }} 33 | env: 34 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | MATRIX_OS: ${{ matrix.os }} 36 | RELEASE_TAG: "PR-${{ steps.metadata.outputs.PR_NUMBER }}" 37 | shell: bash 38 | run: | 39 | pwd 40 | which bash 41 | export PATH=${PATH}:$(dirname $(which bash)) 42 | npm i 43 | case "${MATRIX_OS}" in 44 | ubuntu*) PLATFORM=linux;; 45 | macos*) PLATFORM=macos;; 46 | windows*) 47 | PLATFORM=windows 48 | choco install wget --no-progress 49 | ;; 50 | esac 51 | npm run binaries:${PLATFORM} 52 | npm run electron:build 53 | mkdir -p release-files 54 | ( ls dist_electron/*AppImage && cp -a dist_electron/*AppImage release-files/lift-wallet-${PLATFORM}-${RELEASE_TAG}.AppImage ) || true 55 | ( ls dist_electron/*dmg && cp -a dist_electron/*dmg release-files/lift-wallet-${PLATFORM}-${RELEASE_TAG}.dmg ) || true 56 | ( ls dist_electron/*exe && cp -a dist_electron/*exe release-files/lift-wallet-${PLATFORM}-${RELEASE_TAG}.exe ) || true 57 | - name: Upload built artifacts 58 | uses: actions/upload-artifact@v2 59 | with: 60 | name: release-files 61 | path: release-files 62 | retention-days: 7 63 | 64 | draft-release: 65 | name: Draft release artifacts 66 | runs-on: ubuntu-latest 67 | needs: [build-artifacts] 68 | steps: 69 | - uses: actions/checkout@v2 70 | - name: Make git short-commit/pr-number available to different steps 71 | id: metadata 72 | run: | 73 | echo "::set-output name=COMMIT::$(echo ${GITHUB_SHA} | head -c7)" 74 | echo "::set-output name=PR_NUMBER::$(echo ${GITHUB_REF} | awk -F/ '{print $3}')" 75 | - name: Download release artifacts 76 | uses: actions/download-artifact@v2 77 | with: 78 | name: release-files 79 | path: release-files 80 | - uses: actions/setup-node@v2 81 | with: 82 | node-version: 14 83 | - name: Release PR draft 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | RELEASE_TAG: "pull-request-${{ steps.metadata.outputs.PR_NUMBER }}" 87 | RELEASE_NAME: "Release PR#${{ steps.metadata.outputs.PR_NUMBER }}-${{ steps.metadata.outputs.COMMIT }}" 88 | RELEASE_NOTES: "Automatic release from GH action on this project." 89 | run: | 90 | sudo npm install -g publish-release 91 | set -x 92 | GITHUB_OWNER=$(echo ${GITHUB_REPOSITORY} | awk -F/ '{print $1}') 93 | GITHUB_REPO=$(echo ${GITHUB_REPOSITORY} | awk -F/ '{print $2}') 94 | git tag ${RELEASE_TAG} 95 | echo '{}' > package.json 96 | until publish-release \ 97 | --draft \ 98 | --reuseRelease \ 99 | --reuseDraftOnly \ 100 | --tag ${RELEASE_TAG} \ 101 | --owner ${GITHUB_OWNER} \ 102 | --repo ${GITHUB_REPO} \ 103 | --token ${GITHUB_TOKEN} \ 104 | --name "${RELEASE_NAME}" \ 105 | --notes "${RELEASE_NOTES}" \ 106 | --assets $(ls release-files/* | xargs echo | sed 's| |,|g'); do 107 | sleep 1; 108 | done 109 | 110 | -------------------------------------------------------------------------------- /src/components/WalletPage.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/cardano.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require("child_process") 2 | const util = require('util'); 3 | const exec = util.promisify(require('child_process').exec); 4 | const path = require("path") 5 | import axios from 'axios' 6 | 7 | const baseUrl = 'http://localhost:8090' 8 | const isDevelopment = process.env.NODE_ENV !== 'production' 9 | 10 | export const cardanoPath = isDevelopment 11 | ? path.resolve(__dirname, '..', 'cardano') 12 | : path.resolve(__dirname, '..', '..', 'cardano'); 13 | export const cardanoPlatformPath = process.platform === 'darwin' ? 'macos64': 14 | process.platform === 'win32' ? 'win64': 15 | process.platform === 'linux' ? 'linux64': 16 | process.platform; 17 | 18 | export const dbPath = path.resolve(cardanoPath, 'db'); 19 | export const walletDbPath = path.resolve(cardanoPath, 'wallets'); 20 | export const socketPath = (process.platform == 'win32') ? '\\\\.\\pipe\\cardano-node-mainnet' : path.resolve(cardanoPath, 'socket'); 21 | export const configPath = path.resolve(cardanoPath, 'configs'); 22 | export const topolgoyFile = path.resolve(configPath, 'mainnet-topology.json'); 23 | export const configFile = path.resolve(configPath, 'mainnet-config.json'); 24 | 25 | export const cardanoNodeOptions = [ 26 | '--port', '6000', 27 | '--database-path', dbPath, 28 | '--socket-path', socketPath, 29 | '--config', configFile, 30 | '--topology', topolgoyFile 31 | ] 32 | 33 | export async function getPhrase() { 34 | try { 35 | const addressCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-address'); 36 | const { stdout, stderr } = await exec(`${addressCli} recovery-phrase generate`) 37 | if(stderr) return { error: stderr, phrase: null }; 38 | return { error: null, phrase: stdout }; 39 | }catch(e) { 40 | console.error(e); 41 | return { error: e, phrase: null }; 42 | } 43 | } 44 | 45 | export async function createWallet(name, mnemonic, passphrase) { 46 | try { 47 | const mnemonicList = mnemonic.split(" "); 48 | var result = await axios.post( 49 | `${baseUrl}/v2/wallets`, 50 | { 51 | "name": name, 52 | "mnemonic_sentence": mnemonicList, 53 | "passphrase": passphrase, 54 | "address_pool_gap": 20 55 | }, 56 | { timeout: 10000 }); 57 | return result.data; 58 | }catch(err){ 59 | return err.response.data 60 | } 61 | } 62 | 63 | export async function getWallets() { 64 | try { 65 | var result = await axios.get(`${baseUrl}/v2/wallets`, { timeout: 10000 }) 66 | return result.data; 67 | }catch(err){ 68 | return err.response.data 69 | } 70 | } 71 | 72 | export async function getWallet(walletId) { 73 | try { 74 | var result = await axios.get(`${baseUrl}/v2/wallets/${walletId}`, { timeout: 10000 }) 75 | return result.data; 76 | }catch(err){ 77 | return err.response.data 78 | } 79 | } 80 | 81 | export async function getAddresses(walletId) { 82 | try { 83 | var result = await axios.get(`${baseUrl}/v2/wallets/${walletId}/addresses`, { timeout: 10000 }) 84 | return result.data; 85 | }catch(err){ 86 | return err.response.data 87 | } 88 | } 89 | 90 | export async function validateAddress(addressId) { 91 | try { 92 | var result = await axios.get(`${baseUrl}/v2/addresses/${addressId}`, { timeout: 10000 }) 93 | if(result.status != 200) 94 | return null; 95 | return result.data; 96 | }catch(err){ 97 | return err.response.data 98 | } 99 | } 100 | 101 | export async function getTransactions(walletId) { 102 | try { 103 | var result = await axios.get(`${baseUrl}/v2/wallets/${walletId}/transactions`, { timeout: 10000 }) 104 | return result.data; 105 | }catch(err){ 106 | return err.response.data 107 | } 108 | } 109 | 110 | export async function createTransactions(walletId, transaction) { 111 | try { 112 | var result = await axios.post( 113 | `${baseUrl}/v2/wallets/${walletId}/transactions`, 114 | transaction, 115 | { timeout: 10000 }) 116 | return result.data; 117 | }catch(err){ 118 | return err.response.data; 119 | } 120 | } 121 | 122 | export async function getTransactionFee(walletId, transaction) { 123 | try { 124 | var result = await axios.post( 125 | `${baseUrl}/v2/wallets/${walletId}/payment-fees`, 126 | transaction, 127 | { timeout: 10000 }) 128 | return result.data; 129 | }catch(err){ 130 | return err.response.data; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import { spawn } from 'child_process' 3 | import path from 'path' 4 | import { app, protocol, BrowserWindow, ipcMain } from 'electron' 5 | import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' 6 | import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' 7 | import { 8 | setupWalletDir, 9 | getMnemonic, 10 | getAddresses, 11 | getBalance, 12 | getWallets, 13 | getFee, 14 | sendTransaction, 15 | mintToken, 16 | createWallet, 17 | getTransactions } from './services/wallet.service.js'; 18 | import { 19 | cardanoPath, 20 | cardanoPlatformPath, 21 | cardanoNodeOptions, 22 | getNetworkInfo, 23 | validateAddress } from './cardano' 24 | const isDevelopment = process.env.NODE_ENV !== 'production'; 25 | 26 | // Scheme must be registered before the app is ready 27 | protocol.registerSchemesAsPrivileged([ 28 | { scheme: 'app', privileges: { secure: true, standard: true } } 29 | ]) 30 | 31 | async function createWindow() { 32 | // Create the browser window. 33 | const win = new BrowserWindow({ 34 | width: 1200, 35 | height: 900, 36 | webPreferences: { 37 | // Use pluginOptions.nodeIntegration, leave this alone 38 | // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info 39 | nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION 40 | } 41 | }) 42 | 43 | if (process.env.WEBPACK_DEV_SERVER_URL) { 44 | // Load the url of the dev server if in development mode 45 | await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) 46 | //if (!process.env.IS_TEST) win.webContents.openDevTools() 47 | } else { 48 | createProtocol('app') 49 | // Load the index.html when not in development 50 | win.loadURL('app://./index.html') 51 | } 52 | } 53 | 54 | // Quit when all windows are closed. 55 | app.on('window-all-closed', () => { 56 | // On macOS it is common for applications and their menu bar 57 | // to stay active until the user quits explicitly with Cmd + Q 58 | if (process.platform !== 'darwin') { 59 | app.quit() 60 | } 61 | }) 62 | 63 | app.on('activate', () => { 64 | // On macOS it's common to re-create a window in the app when the 65 | // dock icon is clicked and there are no other windows open. 66 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 67 | }) 68 | 69 | // This method will be called when Electron has finished 70 | // initialization and is ready to create browser windows. 71 | // Some APIs can only be used after this event occurs. 72 | app.on('ready', async () => { 73 | if (isDevelopment && !process.env.IS_TEST) { 74 | // Install Vue Devtools 75 | try { 76 | await installExtension(VUEJS_DEVTOOLS) 77 | } catch (e) { 78 | console.error('Vue Devtools failed to install:', e.toString()) 79 | } 80 | } 81 | createWindow() 82 | setupWalletDir(); 83 | }) 84 | 85 | ///Cardano Operations 86 | let cnode = null; 87 | let walletApi = null; 88 | 89 | ipcMain.on('req:start-cnode', (event) => { 90 | //start cardano-node 91 | if(cnode == null) { 92 | cnode = spawn( 93 | path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-node'), 94 | ['run',...cardanoNodeOptions]) 95 | 96 | event.reply('res:start-cnode', { 'cnode': path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-node') }); 97 | 98 | cnode.stdout.on('data', (data) => { 99 | console.info(`cnode: ${data}`); 100 | }); 101 | 102 | cnode.stderr.on('data', (data) => { 103 | console.error(`cnode err: ${data}`); 104 | }); 105 | } 106 | }) 107 | 108 | 109 | ipcMain.on('req:stop-cnode', (event) => { 110 | if(cnode) { 111 | cnode.kill(); 112 | cnode = null; 113 | } 114 | if(walletApi) { 115 | walletApi.kill(); 116 | walletApi = null 117 | } 118 | 119 | event.reply('res:stop-cnode'); 120 | }) 121 | 122 | ipcMain.on('req:get-network', async (event) => { 123 | const networkInfo = await getNetworkInfo(); 124 | event.reply('res:get-network', { network: networkInfo != null ? networkInfo.data : null }) 125 | }) 126 | 127 | ipcMain.on('req:generate-recovery-phrase', async (event) => { 128 | try{ 129 | console.info("get phrase") 130 | 131 | const recoveryPhrase = await getMnemonic(); 132 | event.reply('res:generate-recovery-phrase', { isSuccessful: true, data: recoveryPhrase }); 133 | }catch(e) { 134 | event.reply('res:generate-recovery-phrase', { isSuccessful: false, data: e.toString() }); 135 | } 136 | }) 137 | 138 | 139 | ipcMain.on('req:add-wallet', async (event, args) => { 140 | try{ 141 | console.log('adding wallet', args) 142 | await createWallet(args.network, args.name, args.mnemonic, args.passphrase); 143 | const balance = await getBalance(args.network, args.name); 144 | const wallet = { 145 | name: args.name, 146 | balance: balance 147 | } 148 | event.reply('res:add-wallet', { isSuccessful: true, data: wallet }); 149 | }catch(e) { 150 | event.reply('res:add-wallet', { isSuccessful: false, data: e.toString() }); 151 | } 152 | }) 153 | 154 | 155 | ipcMain.on('req:get-wallets', async (event, args) => { 156 | const wallets = await getWallets(args.network); 157 | event.reply('res:get-wallets', { wallets: wallets }); 158 | }) 159 | 160 | ipcMain.on('req:get-wallet', async (event, args) => { 161 | try{ 162 | const balance = await getBalance(args.network, args.name); 163 | const wallet = { 164 | name: args.name, 165 | balance: balance 166 | } 167 | event.reply('res:get-wallet', { isSuccessful: true, data: wallet }); 168 | }catch(e) { 169 | event.reply('res:get-wallet', { isSuccessful: false, data: e.toString() }); 170 | } 171 | }) 172 | 173 | ipcMain.on('req:get-addresses', async (event, args) => { 174 | const addresses = await getAddresses(args.network, args.name); 175 | event.reply('res:get-addresses', { addresses: addresses }); 176 | }) 177 | 178 | ipcMain.on('req:validate-address', async (event, args) => { 179 | const address = await validateAddress(args.addressId); 180 | event.reply('res:validate-addresses', { address: address }); 181 | }) 182 | 183 | ipcMain.on('req:get-fee', async (event, args) => { 184 | const fee = await getFee(args.network, args.wallet, args.sendAll, args.amount, args.address); 185 | event.reply('res:get-fee', { fee: fee }); 186 | }) 187 | 188 | ipcMain.on('req:send-transaction', async (event, args) => { 189 | const result = await sendTransaction(args.network, args.wallet, args.sendAll, args.amount, args.address, args.passphrase, args.metadata); 190 | event.reply('res:send-transaction', { transaction: result }); 191 | }) 192 | 193 | ipcMain.on('req:get-transactions', async (event, args) => { 194 | //const transactions = await getTransactions(args.walletId); 195 | const transactions = await getTransactions(args.network, args.wallet); 196 | event.reply('res:get-transactions', { transactions: transactions }); 197 | }) 198 | 199 | //minting assets 200 | ipcMain.on('req:mint-asset', async (event, args) => { 201 | const result = await mintToken(args.network, args.walletName, args.assetName, args.assetAmount, args.passphrase, args.metadata); 202 | event.reply('res:mint-asset', { transaction: result }); 203 | }) 204 | 205 | // Exit cleanly on request from parent process in development mode. 206 | if (isDevelopment) { 207 | if (process.platform === 'win32') { 208 | process.on('message', (data) => { 209 | if (data === 'graceful-exit') { 210 | app.quit() 211 | } 212 | }) 213 | } else { 214 | process.on('SIGTERM', () => { 215 | app.quit() 216 | }) 217 | } 218 | } 219 | 220 | app.on('quit', () => { 221 | if(cnode) cnode.kill(); 222 | if(walletApi) walletApi.kill(); 223 | }) 224 | -------------------------------------------------------------------------------- /src/core/cardano-cli.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { cli, hex_to_ascii } from './common.js'; 3 | import fs from 'fs' 4 | 5 | 6 | 7 | const isDevelopment = process.env.NODE_ENV !== 'production' 8 | 9 | export const cardanoPath = isDevelopment 10 | ? path.resolve(__dirname, '..', 'cardano') 11 | : path.resolve(__dirname, '..', '..', 'cardano'); 12 | 13 | export const cardanoPlatformPath = process.platform === 'darwin' ? 'macos64': 14 | process.platform === 'win32' ? 'win64': 15 | process.platform === 'linux' ? 'linux64': 16 | process.platform; 17 | 18 | 19 | export function buildTxIn(addressUtxos, amount, fee) { 20 | let txIn = []; 21 | let totalUsed = 0; 22 | for(let u of addressUtxos) 23 | { 24 | u.assets = []; 25 | totalUsed += parseInt(u.value); 26 | u.assets.push({ quantity: parseInt(u.value), assetName: 'lovelace' }); 27 | 28 | for(let t of u.tokens) 29 | { 30 | u.assets.push({ 31 | quantity: parseInt(t.quantity), 32 | assetName: `${t.policyId}.${hex_to_ascii(t.assetName)}` 33 | }); 34 | } 35 | 36 | txIn.push(u); 37 | if(totalUsed >= parseInt(amount) + parseInt(fee)) 38 | break; 39 | } 40 | 41 | return txIn; 42 | } 43 | 44 | export function buildTransaction(era, fee, ttl, toAddress, amount, changeAddress, txIns, metadataPath, outputFile, isSendAll){ 45 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 46 | let tx = `"${cardanoCli}" transaction build-raw --${era} --fee ${parseInt(fee)} --ttl ${parseInt(ttl)}`; 47 | let totalUsed = 0; 48 | 49 | for(let txIn of txIns) 50 | { 51 | totalUsed += parseInt(txIn.value) 52 | tx += ` --tx-in ${txIn.txHash}#${txIn.index}`; 53 | } 54 | 55 | if (isSendAll) { 56 | let outputAmount = parseInt(amount) - parseInt(fee); 57 | tx += ` --tx-out ${toAddress}+${outputAmount}`; 58 | } else { 59 | let change = parseInt(totalUsed) - parseInt(amount) - parseInt(fee); 60 | if(change >0) tx += ` --tx-out ${changeAddress}+${change}`; 61 | tx += ` --tx-out ${toAddress}+${parseInt(amount)}`; 62 | } 63 | 64 | if(metadataPath != null) tx += ` --metadata-json-file "${metadataPath}"`; 65 | 66 | tx += ` --out-file "${outputFile}"`; 67 | return tx; 68 | } 69 | 70 | export function getAddressKeyHash(verificationKeyPath) { 71 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 72 | 73 | let cmd = `${cardanoCli}`; 74 | cmd += ` address key-hash`; 75 | cmd += ` --payment-verification-key-file "${verificationKeyPath}"`; 76 | 77 | return cmd; 78 | 79 | } 80 | 81 | export function createMonetaryPolicy(keyHash, policyScriptPath, policyVkeyPath, policySkeyPath) { 82 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 83 | 84 | const policyScript = `{ 85 | "keyHash": "${keyHash}", 86 | "type": "sig" 87 | }`; 88 | 89 | fs.writeFileSync(policyScriptPath, policyScript); 90 | 91 | let cmd = `${cardanoCli}`; 92 | cmd += ` address key-gen`; 93 | cmd += ` --verification-key-file "${policyVkeyPath}"`; 94 | cmd += ` --signing-key-file "${policySkeyPath}"`; 95 | 96 | return cmd; 97 | } 98 | 99 | export function getPolicyId(assetDir) { 100 | 101 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 102 | 103 | let cmd = `${cardanoCli}` 104 | cmd += ` transaction policyid`; 105 | cmd += ` --script-file "${assetDir}"`; 106 | 107 | return cmd; 108 | 109 | } 110 | 111 | export function buildMintTransaction(era, fee, ttl, toAddress, assetId, assetName, mintAmount, txIns, metadataPath, outputFile){ 112 | 113 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 114 | 115 | let tx = `"${cardanoCli}" transaction build-raw --${era} --fee ${parseInt(fee)} --ttl ${parseInt(ttl)}`; 116 | let totalValue = 0; 117 | let totalAssets = []; 118 | for(let txIn of txIns) 119 | { 120 | totalValue += parseInt(txIn.value) 121 | tx += ` --tx-in ${txIn.txHash}#${txIn.index}`; 122 | console.log(txIn.assets); 123 | for(let asset of txIn.assets) { 124 | //see if we have already added an asset to our total list 125 | var existingAsset = totalAssets.find(c => c.assetName == asset.assetName); 126 | //if we haven't add it to the list as the initial value 127 | if(existingAsset == undefined) { 128 | totalAssets.push({ 129 | quantity: parseInt(asset.quantity), 130 | assetName: asset.assetName 131 | }); 132 | }else { //if we have already added the asset, lets just increment the quantity from another UTXO 133 | existingAsset.quantity += parseInt(asset.quantity); 134 | } 135 | } 136 | } 137 | 138 | tx += ` --tx-out "${toAddress}` 139 | let mintingTokenExists = false; 140 | //accommodate X number of assets 141 | console.log(totalAssets); 142 | for(let asset of totalAssets) { 143 | if(asset.assetName == 'lovelace') 144 | asset.quantity -= parseInt(fee); 145 | 146 | if(asset.assetName == `${assetId}.${assetName}`){ 147 | asset.quantity += parseInt(mintAmount); 148 | mintingTokenExists = true; 149 | } 150 | 151 | tx += `+${asset.quantity} ${asset.assetName}`; 152 | } 153 | if(!mintingTokenExists) 154 | tx += `+${mintAmount} ${assetId}.${assetName}"`; 155 | else 156 | tx += '"'; 157 | 158 | tx += ` --mint="${mintAmount} ${assetId}.${assetName}"`; 159 | if(metadataPath != null) tx += ` --metadata-json-file "${metadataPath}"`; 160 | tx += ` --out-file "${outputFile}"`; 161 | 162 | return tx; 163 | } 164 | 165 | export function calculateMinFee(txBody, utxoInCount, utxoOutCount, witness, byronWitness, protocolParamsFile) { 166 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 167 | let txFee = `"${cardanoCli}" transaction calculate-min-fee`; 168 | txFee += ` --tx-body-file "${txBody}"`; 169 | txFee += ` --tx-in-count ${utxoInCount}`; 170 | txFee += ` --tx-out-count ${utxoOutCount}`; 171 | txFee += ` --witness-count ${witness}`; 172 | txFee += ` --byron-witness-count ${byronWitness}`; 173 | txFee += ` --protocol-params-file "${protocolParamsFile}"`; 174 | return txFee; 175 | } 176 | 177 | export function signTransaction(network, magic, signingKeyPaths, rawTxBody, signTxFile, policyScriptPath, policySkeyPath) { 178 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 179 | 180 | if(network == 'testnet') network = 'testnet-magic'; 181 | 182 | let txSign = `"${cardanoCli}" transaction sign`; 183 | txSign += ` --${network}`; 184 | if(magic != null) txSign += ` ${magic}`; 185 | 186 | signingKeyPaths.forEach(sk => { 187 | txSign += ` --signing-key-file "${sk}"`; 188 | }) 189 | if(policySkeyPath != null) txSign += ` --signing-key-file "${policySkeyPath}"`; 190 | if(policyScriptPath != null) txSign += ` --script-file "${policyScriptPath}"`; 191 | 192 | txSign += ` --tx-body-file "${rawTxBody}"`; 193 | txSign += ` --out-file "${signTxFile}"`; 194 | return txSign; 195 | } 196 | 197 | export function createPaymentVerificationKey(paymentSigningFile, extendedVerificationKeyFile) { 198 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 199 | // build evkey+vkey 200 | let cmd = `${cardanoCli} key verification-key` 201 | cmd += ` --signing-key-file "${paymentSigningFile}"`; 202 | cmd += ` --verification-key-file "${extendedVerificationKeyFile}"`; 203 | 204 | return cmd 205 | } 206 | 207 | export function createExtendedVerificationKey(extendedVerificationKeyFile, verificationKeyFile) { 208 | const cardanoCli = path.resolve('.', cardanoPath, cardanoPlatformPath, 'cardano-cli'); 209 | var cmd = `${cardanoCli} key non-extended-key`; 210 | cmd += ` --extended-verification-key-file "${extendedVerificationKeyFile}"`; 211 | cmd += ` --verification-key-file "${verificationKeyFile}"`; 212 | 213 | return cmd 214 | } 215 | -------------------------------------------------------------------------------- /src/components/wallet/AddWallet.vue: -------------------------------------------------------------------------------- 1 | 165 | 166 | 269 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/components/wallet/WalletDetails.vue: -------------------------------------------------------------------------------- 1 | 228 | 229 | 617 | 618 | 627 | -------------------------------------------------------------------------------- /src/services/wallet.service.js: -------------------------------------------------------------------------------- 1 | import { cli, decrypt, encrypt, hex_to_ascii } from '../core/common'; 2 | import { 3 | getMnemonicCmd, 4 | getRootCmd, 5 | getPublicCmd, 6 | getChildCmd, 7 | getBaseAddrCmd, 8 | getPaymentAddrCmd } from '../core/cardano-addresses.js'; 9 | import { 10 | buildTxIn, 11 | buildTransaction, 12 | buildMintTransaction, 13 | calculateMinFee, 14 | createPaymentVerificationKey, 15 | createExtendedVerificationKey, 16 | getAddressKeyHash, 17 | createMonetaryPolicy, 18 | getPolicyId, 19 | signTransaction } from '../core/cardano-cli.js'; 20 | import { 21 | getProtocolParams, 22 | getUtxos, 23 | getCurrentSlotNo, 24 | submitTransaction, 25 | getTransactionsByAddresses, 26 | getTransactionsDetails } from '../core/dandelion.js' 27 | import util from 'util'; 28 | import path from 'path'; 29 | import fs from 'fs'; 30 | import { exec } from 'child_process' 31 | import lib from 'cardano-crypto.js' 32 | 33 | const cmd = util.promisify(exec); 34 | 35 | const isDevelopment = process.env.NODE_ENV !== 'production' 36 | const appPath = path.resolve(process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share"), 'lift-wallet'); 37 | 38 | const walletsPath = isDevelopment 39 | ? path.resolve(__dirname, '..', 'cardano', 'wallets') 40 | : path.resolve(appPath , 'wallets'); 41 | 42 | const assetsPath = isDevelopment 43 | ? path.resolve(__dirname, '..', 'cardano', 'assets') 44 | : path.resolve(appPath , 'assets'); 45 | 46 | const accountPrvFile = 'account.xprv'; 47 | const accountPubFile = 'account.xpub'; 48 | const paymentFile = 'payment.addr'; 49 | const paymentSigningKeyFile = 'payment.skey'; 50 | const extendedVerificationKeyFile = 'payment.evkey'; 51 | const verificationKeyFile = 'payment.vkey'; 52 | const changeFile = 'change.addr'; 53 | const protocolParamsFile = 'protocolParams.json'; 54 | const draftTxFile = 'draft.tx'; 55 | const rawTxFile = 'raw.tx'; 56 | const signedTxFile = 'signed.tx'; 57 | const policyScriptFile = 'policyScript.json'; 58 | const policySigningKeyFile = 'policy.skey'; 59 | const policyVerificationKeyFile = 'policy.vkey'; 60 | 61 | export async function setupWalletDir() { 62 | if(!isDevelopment) { 63 | if(!fs.existsSync(appPath)) { 64 | fs.mkdirSync(appPath); 65 | } 66 | } 67 | 68 | if(!fs.existsSync(walletsPath)){ 69 | fs.mkdirSync(walletsPath); 70 | } 71 | 72 | if(!fs.existsSync(path.resolve(walletsPath, 'testnet'))) 73 | fs.mkdirSync(path.resolve(walletsPath, 'testnet')) 74 | 75 | if(!fs.existsSync(path.resolve(walletsPath, 'mainnet'))) 76 | fs.mkdirSync(path.resolve(walletsPath, 'mainnet')) 77 | } 78 | 79 | export async function getMnemonic(){ 80 | //right now we are hardcoding 24 words 81 | // lets pass in a number. maybe we use a set of word lengths 82 | // 12, 15, 18, 21, 24 83 | const { stdout, stderr } = await cmd(getMnemonicCmd(24)); 84 | if(stderr) throw stderr; 85 | 86 | return stdout.replace('\n', ''); 87 | } 88 | 89 | export async function createWallet(network, name, mnemonic, passphrase) { 90 | 91 | //if we already have a wallet for this network with this name 92 | // prevent the wallet creation. 93 | const walletDir = path.resolve(walletsPath, network, name); 94 | if(fs.existsSync(walletDir)) throw "Wallet already exists"; 95 | 96 | //root key 97 | const rootKey = await cmd(getRootCmd(mnemonic)); 98 | if(rootKey.stderr) throw rootKey.stderr; 99 | 100 | //account private 101 | const accountPrv = await cmd(getChildCmd(rootKey.stdout, "1852H/1815H/0H")); 102 | if(accountPrv.stderr) throw accountPrv.stderr; 103 | 104 | //account public 105 | const accountPub = await cmd(getPublicCmd(accountPrv.stdout)); 106 | if(accountPub.stderr) throw accountPub.stderr; 107 | 108 | ///TODO: We maybe want to think about moving this to be on-demand. 109 | //payment priv/pub keys (needed to get verification keys) 110 | const paymentPrv = await cmd(getChildCmd(accountPrv.stdout, "0/0")); 111 | if(paymentPrv.stderr) throw paymentPrv.stderr; 112 | const paymentPub = await cmd(getChildCmd(accountPub.stdout, "0/0")); 113 | if(paymentPub.stderr) throw paymentPub.stderr; 114 | //payment signiing key (needed to get verification keys) 115 | const paymentSKeyPath = path.resolve(walletDir, paymentSigningKeyFile); 116 | const paymentSigningKey = getBufferHexFromFile(paymentPrv.stdout).slice(0, 128) + getBufferHexFromFile(paymentPub.stdout); 117 | const paymentSKey = `{ 118 | "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", 119 | "description": "Payment Signing Key", 120 | "cborHex": "5880${paymentSigningKey}" 121 | }` 122 | // public [extended] verification key 123 | const extendedVerificationKeyPath = path.resolve(walletDir, extendedVerificationKeyFile); 124 | const verificationKeyPath = path.resolve(walletDir, verificationKeyFile); 125 | 126 | //stake public 127 | const stakePub = await cmd(getChildCmd(accountPub.stdout, "2/0")); 128 | if(stakePub.stderr) throw stakePub.stderr; 129 | 130 | const addresses = []; 131 | const changes = []; 132 | for(let i = 0; i < 20; i++) { 133 | //public payment key 134 | const paymentPub = await cmd(getChildCmd(accountPub.stdout, `0/${i}`)); 135 | if(paymentPub.stderr) throw paymentPub.stderr; 136 | 137 | //enterprise address 138 | const basePaymentAddr = await cmd(getBaseAddrCmd(paymentPub.stdout, network)); 139 | if(basePaymentAddr.stderr) throw basePaymentAddr.stderr; 140 | 141 | //payment address 142 | const paymentAddr = await cmd(getPaymentAddrCmd(basePaymentAddr.stdout, stakePub.stdout)); 143 | if(paymentAddr.stderr) throw paymentAddr.stderr; 144 | 145 | //public change key 146 | const changePub = await cmd(getChildCmd(accountPub.stdout, `1/${i}`)); 147 | if(changePub.stderr) throw changePub.stderr; 148 | 149 | //enterprise change address 150 | const baseChangeAddr = await cmd(getBaseAddrCmd(changePub.stdout, network)); 151 | if(baseChangeAddr.stderr) throw baseChangeAddr.stderr; 152 | 153 | //change address 154 | const changeAddr = await cmd(getPaymentAddrCmd(baseChangeAddr.stdout, stakePub.stdout)); 155 | if(changeAddr.stderr) throw changeAddr.stderr; 156 | 157 | addresses.push({ index: i, address: paymentAddr.stdout }); 158 | changes.push({ index: i, address: changeAddr.stdout }); 159 | } 160 | 161 | //keys/addresses to save 162 | //account prv (encrypted) 163 | //account pub 164 | //10 - payment addresses 165 | fs.mkdirSync(walletDir); 166 | // write publicVerificationKey now that wallet dir exists 167 | fs.writeFileSync(path.resolve(walletDir, paymentSKeyPath), paymentSKey); 168 | let paymentVerificationKey = createPaymentVerificationKey(paymentSKeyPath, extendedVerificationKeyPath); 169 | await cli(paymentVerificationKey); 170 | let extendedVerificationKey = createExtendedVerificationKey(extendedVerificationKeyPath, verificationKeyPath) 171 | await cli(extendedVerificationKey); 172 | //// cleanup paymentskey 173 | if (fs.existsSync(paymentSKeyPath)) fs.unlinkSync(paymentSKeyPath); 174 | 175 | fs.writeFileSync(path.resolve(walletDir, accountPrvFile), accountPrv.stdout); 176 | fs.writeFileSync(path.resolve(walletDir, accountPubFile), accountPub.stdout); 177 | encrypt( 178 | path.resolve(walletDir, accountPrvFile), 179 | path.resolve(walletDir, accountPubFile), 180 | passphrase); 181 | fs.writeFileSync(path.resolve(walletDir, paymentFile), JSON.stringify(addresses)); 182 | fs.writeFileSync(path.resolve(walletDir, changeFile), JSON.stringify(changes)); 183 | } 184 | 185 | export async function getWallets(network) { 186 | const networkPath = path.resolve(walletsPath, network); 187 | const walletDirs = getDirectories(networkPath); 188 | let wallets = []; 189 | for(let i = 0; i < walletDirs.length; i++) { 190 | var balance=null; 191 | try{ 192 | balance = await getBalance(network, walletDirs[i]); 193 | }catch(e){ 194 | console.log(e.message); 195 | } 196 | if(balance!=null){ 197 | wallets.push({ 198 | name: walletDirs[i], 199 | balance: balance 200 | }); 201 | } 202 | } 203 | return wallets; 204 | } 205 | 206 | export async function getAddresses(network, name) { 207 | const walletDir = path.resolve(walletsPath, network, name); 208 | return JSON.parse(fs.readFileSync(path.resolve(walletDir, paymentFile))) 209 | } 210 | 211 | export async function getBalance(network, name) { 212 | const walletDir = path.resolve(walletsPath, network, name); 213 | const addresses = JSON.parse(fs.readFileSync(path.resolve(walletDir, paymentFile))) 214 | const changes = JSON.parse(fs.readFileSync(path.resolve(walletDir, changeFile))) 215 | 216 | const addressUtxos = await getUtxos( 217 | network, 218 | [...addresses.map((a) => a.address),...changes.map((a) => a.address)]); 219 | return getTotalUtxoBalance(addressUtxos); 220 | } 221 | 222 | export async function getFee(network, name, sendAll, amount, toAddress) { 223 | const walletDir = path.resolve(walletsPath, network, name); 224 | 225 | //tx/key file paths 226 | const txDraftPath = path.resolve(walletDir, draftTxFile); 227 | 228 | //gather payment/change addresses for utxos 229 | const addresses = JSON.parse(fs.readFileSync(path.resolve(walletDir, paymentFile))) 230 | const changes = JSON.parse(fs.readFileSync(path.resolve(walletDir, changeFile))) 231 | 232 | //UTxOs 233 | const addressUtxos = await getUtxos( 234 | network, 235 | [...addresses.map((a) => a.address), ...changes.map((a) => a.address)]); 236 | 237 | //get draft tx-ins 238 | let draftTxIns = buildTxIn(addressUtxos, amount, 0); 239 | 240 | //build draft transaction 241 | let draftTx = buildTransaction('allegra-era', 0, 0, toAddress, amount, changes[0].address, draftTxIns, null, txDraftPath, sendAll) 242 | await cli(draftTx); 243 | 244 | //get protocol parameters 245 | const protocolParamsPath = await refreshProtocolParametersFile(network) 246 | 247 | //calculate fees 248 | const calculateFee = calculateMinFee(txDraftPath, addressUtxos.length, 2, 1, 0, protocolParamsPath); 249 | const feeResult = await cli(calculateFee); 250 | //originally tried to just calculate the fee locally 251 | // but had issues when trying to use multiple --tx-in 252 | //minFeeA * txSize + minFeeB 253 | //note the output of the 'calculate-min-fee' is: 'XXXX Lovelace' 254 | // this is why i split and take index 0 255 | return feeResult.stdout.split(' ')[0]; 256 | } 257 | 258 | //add ability to send custom ada amount and to address 259 | export async function mintToken(network, walletName, assetName, tokenAmount, passphrase, metadataPath) { 260 | const walletDir = path.resolve(walletsPath, network, walletName); 261 | 262 | //if we already have a policy for an asset with this name 263 | const assetDir = path.resolve(walletDir, assetName); 264 | const newAsset = true; 265 | 266 | const txDraftPath = path.resolve(assetDir, draftTxFile); 267 | const txRawPath = path.resolve(assetDir, rawTxFile); 268 | const txSignedPath = path.resolve(assetDir, signedTxFile); 269 | const policyScriptPath = path.resolve(assetDir, policyScriptFile); 270 | const policySkeyPath = path.resolve(assetDir, policySigningKeyFile); 271 | const policyVkeyPath = path.resolve(assetDir, policyVerificationKeyFile); 272 | const signingKeyPaths = []; 273 | 274 | //Step 1) Create a Token Policy 275 | if(newAsset) 276 | { 277 | if (!fs.existsSync(assetDir)){ 278 | fs.mkdirSync(assetDir); 279 | } 280 | //get the key hash of the verification key of the wallet 281 | const verificationKeyPath = path.resolve(walletDir, verificationKeyFile); 282 | const keyHashCmdOutput = await cli(getAddressKeyHash(verificationKeyPath)); 283 | const keyHash = keyHashCmdOutput.stdout.replace(/[\n\r]/g, ''); 284 | 285 | //lets create the monetary policy for the asset 286 | let monetaryPolicy = await createMonetaryPolicy(keyHash, policyScriptPath, policyVkeyPath, policySkeyPath); 287 | await cli(monetaryPolicy); 288 | } 289 | 290 | //Step 2) Get Protocol Params 291 | const protocolParamsPath = await refreshProtocolParametersFile(network); 292 | 293 | //Step 3) Get UTXOs 294 | const addresses = JSON.parse(fs.readFileSync(path.resolve(walletDir, paymentFile))); 295 | const changes = JSON.parse(fs.readFileSync(path.resolve(walletDir, changeFile))) 296 | const addressUtxos = await getUtxos( 297 | network, 298 | [...addresses.map((a) => a.address), ...changes.map((a) => a.address)]); 299 | //TODO: pass in address to send 300 | const tokenDestinationAddress = addresses[0].address; 301 | 302 | //Step 4) Build Draft Tx 303 | let draftTxIns = buildTxIn(addressUtxos, getBalance(network, walletName), 0); 304 | const assetIdCmdOutput = await cli(getPolicyId(policyScriptPath)); 305 | const assetId = assetIdCmdOutput.stdout.replace(/[\n\r]/g, '') 306 | 307 | let draftTx = buildMintTransaction('mary-era', 0, 0, tokenDestinationAddress, assetId, assetName, tokenAmount, draftTxIns, metadataPath, txDraftPath); 308 | await cli(draftTx); 309 | 310 | ////Step 5) Calculute Fees 311 | const calculateFee = calculateMinFee(txDraftPath, addressUtxos.length, 2, 1, 0, protocolParamsPath); 312 | const feeResult = await cli(calculateFee); 313 | const fee = feeResult.stdout.split(' ')[0]; 314 | 315 | //Step 6) Build Raw Tx 316 | const slotNo = await getCurrentSlotNo(network); 317 | const ttl = slotNo + 1000; 318 | let rawTx = buildMintTransaction('mary-era', fee, ttl, tokenDestinationAddress, assetId, assetName, tokenAmount, draftTxIns, metadataPath, txRawPath); 319 | await cli(rawTx); 320 | 321 | await getSigningKeys(draftTxIns, passphrase, walletDir, signingKeyPaths, addresses, changes); 322 | 323 | //Step 7) Sign Tx 324 | const signedTx = signTransaction(network, 1097911063, signingKeyPaths, txRawPath, txSignedPath, policyScriptPath, policySkeyPath); 325 | await cli(signedTx); 326 | const signedtxContents = JSON.parse(fs.readFileSync(txSignedPath)); 327 | 328 | 329 | let result = { transactionId: null, error: null }; 330 | try{ 331 | //Step 8) Submit Tx 332 | result.transactionId = await submitTransaction(network, signedtxContents); 333 | 334 | }catch(err) { 335 | console.error(err); 336 | if(err.response.data != undefined) { 337 | console.log(err.response.data); 338 | result.error = err.response.data; 339 | } 340 | else { 341 | console.log(err); 342 | result.error = err; 343 | } 344 | } 345 | 346 | //clean up 347 | if(fs.existsSync(txDraftPath)) fs.unlinkSync(txDraftPath); 348 | if(fs.existsSync(txRawPath)) fs.unlinkSync(txRawPath); 349 | if(fs.existsSync(txSignedPath)) fs.unlinkSync(txSignedPath); 350 | 351 | signingKeyPaths.forEach(sk => { 352 | if(fs.existsSync(sk)) fs.unlinkSync(sk); 353 | }) 354 | 355 | return result; 356 | } 357 | 358 | export async function refreshProtocolParametersFile(network) { 359 | 360 | const protocolParamsPath = path.resolve(walletsPath, network, protocolParamsFile); 361 | const protocolParams = await getProtocolParams(network); 362 | fs.writeFileSync( 363 | protocolParamsPath, 364 | Buffer.from(JSON.stringify(protocolParams))); 365 | 366 | return protocolParamsPath 367 | 368 | } 369 | 370 | 371 | export async function sendTransaction(network, name, sendAll, amount, toAddress, passphrase, metadataPath) { 372 | const walletDir = path.resolve(walletsPath, network, name); 373 | 374 | //tx/key file paths 375 | const txDraftPath = path.resolve(walletDir, draftTxFile); 376 | const txRawPath = path.resolve(walletDir, rawTxFile); 377 | const txSignedPath = path.resolve(walletDir, signedTxFile); 378 | const signingKeyPaths = []; 379 | 380 | let result = { transactionId: null, error: null }; 381 | try { 382 | //gather payment/change addresses for utxos 383 | const addresses = JSON.parse(fs.readFileSync(path.resolve(walletDir, paymentFile))) 384 | const changes = JSON.parse(fs.readFileSync(path.resolve(walletDir, changeFile))) 385 | 386 | //UTxOs 387 | const addressUtxos = await getUtxos( 388 | network, 389 | [...addresses.map((a) => a.address), ...changes.map((a) => a.address)]); 390 | 391 | //get draft tx-ins 392 | let draftTxIns = buildTxIn(addressUtxos, amount, 0); 393 | 394 | //build draft transaction 395 | let draftTx = buildTransaction('allegra-era', 0, 0, toAddress, amount, changes[0].address, draftTxIns, metadataPath, txDraftPath, sendAll) 396 | await cli(draftTx); 397 | 398 | //refresh protocol parameters 399 | const protocolParamsPath = await refreshProtocolParametersFile(network) 400 | 401 | //calculate fees 402 | const calculateFee = calculateMinFee(txDraftPath, addressUtxos.length, 2, 1, 0, protocolParamsPath); 403 | const feeResult = await cli(calculateFee); 404 | //originally tried to just calculate the fee locally 405 | // but had issues when trying to use multiple --tx-in 406 | //minFeeA * txSize + minFeeB 407 | //note the output of the 'calculate-min-fee' is: 'XXXX Lovelace' 408 | // this is why i split and take index 0 409 | const fee = feeResult.stdout.split(' ')[0]; 410 | 411 | //get current slot no to calculate ttl 412 | const slotNo = await getCurrentSlotNo(network); 413 | const ttl = slotNo + 1000; 414 | 415 | //get draft tx-ins 416 | let rawTxIns = buildTxIn(addressUtxos, amount, fee); 417 | 418 | //build raw transaction 419 | let rawTx = buildTransaction('allegra-era', fee, ttl, toAddress, amount, changes[0].address, rawTxIns, metadataPath, txRawPath, sendAll) 420 | await cli(rawTx); 421 | 422 | await getSigningKeys(draftTxIns, passphrase, walletDir, signingKeyPaths, addresses, changes); 423 | 424 | const signedTx = signTransaction(network, 1097911063, signingKeyPaths, txRawPath, txSignedPath); 425 | await cli(signedTx); 426 | 427 | //send transaction 428 | //get signed tx binary 429 | // var dataHex = JSON.parse(fs.readFileSync(txSignedPath)).cborHex 430 | // var dataBinary = getBinaryFromHexString(dataHex) 431 | var signedtxContents = JSON.parse(fs.readFileSync(txSignedPath)); 432 | 433 | //submit transaction to dandelion 434 | result.transactionId = await submitTransaction(network, signedtxContents); 435 | }catch(err) { 436 | console.error(err); 437 | if(err.response.data != undefined) { 438 | console.log(err.response.data); 439 | result.error = err.response.data; 440 | } 441 | else { 442 | console.log(err); 443 | result.error = err; 444 | } 445 | } 446 | 447 | //clean up 448 | if(fs.existsSync(txDraftPath)) fs.unlinkSync(txDraftPath); 449 | if(fs.existsSync(txRawPath)) fs.unlinkSync(txRawPath); 450 | if(fs.existsSync(txSignedPath)) fs.unlinkSync(txSignedPath); 451 | 452 | signingKeyPaths.forEach(sk => { 453 | if(fs.existsSync(sk)) fs.unlinkSync(sk); 454 | }) 455 | 456 | return result; 457 | } 458 | 459 | export async function getTransactions(network, name) { 460 | const walletDir = path.resolve(walletsPath, network, name); 461 | 462 | //gather payment/change addresses for utxos 463 | const payments = JSON.parse(fs.readFileSync(path.resolve(walletDir, paymentFile))) 464 | const changes = JSON.parse(fs.readFileSync(path.resolve(walletDir, changeFile))) 465 | const addresses = [...payments.map((a) => a.address), ...changes.map((a) => a.address)] 466 | 467 | //get transactions by addresses 468 | const transactions = await getTransactionsByAddresses(network, addresses); 469 | 470 | const transactionsDetails = await getTransactionsDetails( 471 | network, 472 | [...transactions.map((t) => t.tx_hash)] 473 | ) 474 | 475 | let transactionHistory = [] 476 | transactionsDetails.forEach(td => { 477 | const inputFound = td.inputs.filter(i => addresses.includes(i.address)) 478 | const outputFound = td.outputs.filter(o => addresses.includes(o.address)) 479 | 480 | if(inputFound.length > 0) { 481 | let inputSum = 0; 482 | inputFound.forEach(i => inputSum += parseInt(i.value)); 483 | let outputSum = 0; 484 | outputFound.forEach(i => outputSum += parseInt(i.value)); 485 | 486 | transactionHistory.push({ 487 | hash: td.hash, 488 | direction: 'Sent', 489 | datetime: td.includedAt, 490 | amount: parseInt(inputSum - outputSum)/1000000 491 | }) 492 | }else if(outputFound.length > 0) { 493 | let outputSum = 0; 494 | outputFound.forEach(i => outputSum += i.value); 495 | 496 | transactionHistory.push({ 497 | hash: td.hash, 498 | direction: 'Received', 499 | datetime: td.includedAt, 500 | amount: parseInt(outputSum)/1000000 501 | }) 502 | } 503 | }); 504 | transactionHistory.sort((a, b) => new Date(b.datetime) - new Date(a.datetime)); 505 | 506 | return transactionHistory; 507 | } 508 | 509 | function getTotalUtxoBalance(utxos) { 510 | let total = 0; 511 | utxos.forEach(o => total += parseInt(o.value)) 512 | return total; 513 | } 514 | 515 | function getDirectories (source) { 516 | return fs.readdirSync(source) 517 | .filter(f => { 518 | return fs.statSync(path.resolve(source, f)).isDirectory(); 519 | }); 520 | } 521 | 522 | function getBufferHexFromFile(hex) { 523 | return lib.bech32.decode(hex).data.toString('hex'); 524 | } 525 | 526 | function getBinaryFromHexString(hexString) { 527 | return new Uint8Array(hexString.match(/.{1,2}/g).map(b => parseInt(b, 16))); 528 | } 529 | 530 | async function getSigningKeys(draftTxIns, passphrase, walletDir, signingKeyPaths, addresses, changes){ 531 | //create signing keys 532 | //decrypt account prv 533 | const accountPrv = decrypt( 534 | path.resolve(walletDir, accountPrvFile), 535 | path.resolve(walletDir, accountPubFile), 536 | passphrase); 537 | 538 | for(let i = 0; i < draftTxIns.length; i++) { 539 | const txIn = draftTxIns[i]; 540 | //figure out if it is external or internal address 541 | const payment = addresses.find(a => a.address == txIn.address) 542 | const change = changes.find(c => c.address == txIn.address) 543 | 544 | if(payment != undefined) 545 | { 546 | //payment private/public 547 | const paymentPrv = await cmd(getChildCmd(accountPrv, `0/${payment.index}`)); 548 | if(paymentPrv.stderr) throw paymentPrv.stderr; 549 | const paymentPub = await cmd(getPublicCmd(paymentPrv.stdout)); 550 | if(paymentPub.stderr) throw paymentPub.stderr; 551 | 552 | //payment signing keys 553 | const paymentSigningKey = getBufferHexFromFile(paymentPrv.stdout).slice(0, 128) + getBufferHexFromFile(paymentPub.stdout) 554 | const paymentSigningKeyFile = `payment.${payment.index}.skey` 555 | const paymentSKeyPath = path.resolve(walletDir, paymentSigningKeyFile); 556 | 557 | fs.writeFileSync(paymentSKeyPath, `{ 558 | "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", 559 | "description": "Payment Signing Key", 560 | "cborHex": "5880${paymentSigningKey}" 561 | }`); 562 | signingKeyPaths.push(paymentSKeyPath) 563 | }else if(change != undefined) { 564 | //change private/public 565 | const changePrv = await cmd(getChildCmd(accountPrv, `1/${change.index}`)); 566 | if(changePrv.stderr) throw changePrv.stderr; 567 | const changePub = await cmd(getPublicCmd(changePrv.stdout)); 568 | if(changePub.stderr) throw changePub.stderr; 569 | 570 | //change signing keys 571 | const changeSigningKey = getBufferHexFromFile(changePrv.stdout).slice(0, 128) + getBufferHexFromFile(changePub.stdout) 572 | const changeSigningKeyFile = `change.${change.index}.skey` 573 | const changeSKeyPath = path.resolve(walletDir, changeSigningKeyFile); 574 | 575 | fs.writeFileSync(changeSKeyPath, `{ 576 | "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", 577 | "description": "Change Signing Key", 578 | "cborHex": "5880${changeSigningKey}" 579 | }`); 580 | signingKeyPaths.push(changeSKeyPath) 581 | }else { 582 | //wtf? 583 | console.log('Unable to find address from tx input'); 584 | } 585 | } 586 | } 587 | --------------------------------------------------------------------------------