├── .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 |
2 |
3 |
4 |
5 |
6 | Doesn't look like you have any wallets
9 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
27 |
--------------------------------------------------------------------------------
/src/components/MainView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/wallet/wallet-details/WalletSyncing.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{progress}}%
12 | Syncing...
13 |
14 |
15 |
16 |
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 |
14 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
15 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ option }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 | [](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 | 
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 |
2 |
3 |
7 |
8 |
12 |
13 |
14 |
LIFT
15 |
16 |
17 |
18 |
19 |
30 |
34 |
38 | Testnet
39 |
40 |
44 | Mainnet
45 |
46 |
47 |
48 |
49 | mdi-flag
50 | {{ $t('lang.language') }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{
9 | $t("lang.wallet.add")
10 | }}
11 |
12 |
13 |
18 |
19 | {{ wallet.name }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
40 |
41 |
42 |
43 |
44 |
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 |
2 |
3 |
4 |
5 | Cancel Wallet Creation
11 |
12 |
13 |
14 |
15 |
16 |
17 | Create / Import
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Create a Wallet
27 | I do not already have a wallet.
30 |
31 |
32 | Create Now
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Import a Wallet
42 | I already have a wallet. I have my mnemonic
44 | words.
46 |
47 |
48 | Import Now
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Generate Mnemonics
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Record Your Mnemonic Words
70 | After this step, Perdix will not be able to show you these
72 | words. Please be responsible and write these down. You
73 | will be required to re-enter this words during the next
74 | step.
76 |
77 |
78 | {{ mnemonic }}
79 |
80 |
81 |
82 |
83 | Continue
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {{ $t('lang.wallet.add_wallet') }}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Complete Form
105 | Please fill out the form to complete your wallet
107 | creation.
109 |
110 |
111 |
119 |
120 |
121 |
128 |
129 |
130 |
142 |
143 |
144 |
145 |
151 | Submit
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
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 |
2 |
3 |
4 |
5 |
6 | Total: {{ displayADA(wallet.balance) }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 | Transactions
22 | Receive
23 | Send
24 | Mint
25 |
26 |
27 |
28 |
32 |
33 |
34 | Doesn't look like you have any
36 | transactions
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 | {{ tx.amount }} ADA
49 |
56 | {{ tx.direction }}
57 |
58 |
59 |
60 | {{ tx.hash }}
69 |
70 |
71 | {{ getFormattedDate(tx.datetime) }}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Index
87 |
88 |
89 | Address
90 |
91 |
92 |
93 |
94 |
95 | {{ item.index }}
96 | {{ item.address }}
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
119 |
120 |
132 |
133 |
134 |
140 |
141 | : {{ sendForm.totalFormatted }}
143 |
144 |
145 |
160 |
161 |
162 |
166 |
167 |
168 |
169 |
175 | Submit
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
191 |
192 |
196 |
197 |
206 |
207 |
208 |
212 |
213 |
214 |
215 | Mint
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
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 |
--------------------------------------------------------------------------------