PoK commandline application

Installation

  1. Install Node
  2. Install npx globally: npm install -g npx
  3. Install node packages. In project root folder run: npm install
  4. Change configuration object(row 14-19 in code sample)
  5. Running: npm start

Commandline application

Code

import axios from "axios";
const readline = require('readline');
import { WebSocket } from 'ws';
import { Wallet } from 'ethers';


const appConfig = {
    NOTARDEC_URL: 'https://uat-api.notardec.com/v0.5',
    NOTARDEC_WS_URL: 'wss://uat-api.notardec.com/v0.5/ws',
    STEP: '0'
}

const config = {
    API_KEY: 'enter your api key',
    ARTIFACT_NAME: 'enter your artifact name',
    ARTIFACT_AUTHOR: "enter your artifact author",
    ARTIFACT_ID: "enter your artifact id",
    LINK_ARTIFACT_ID: "enter your link artifact id",
    PRIVATE_KEY: ''
}

let pokData = {
    POK_RESULT: '',
    CHALLENGE_ID: '',
    ADDRESS: '',
    CHALLENGE: ''
};

let assetData = {

}

let transactions = {
    ASSET_TRANSACTION: '',
    LINK_TRANSACTION: ''
}

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

async function settingUp(config){
    if (!config.API_KEY){
        console.error('You must enter the API key')
        return 0;
    }
    if (!config.ARTIFACT_ID && !config.ARTIFACT_NAME){
        console.error('You must enter the artifact id or artifact name')
        return 0;
    }

    if (!config.ARTIFACT_AUTHOR){
        console.error('You must enter the author address')
        return 0;
    }

    if (!config.LINK_ARTIFACT_ID){
        console.error('You must enter the link artifact address')
        return 0;
    }
    if (!config.PRIVATE_KEY){
        console.error('You must enter the private key')
        return 0;
    }
    console.log('Your configuration:')
    console.log(config)
}

async function createOrGetArtifact(config,transactions) {
    try {
        if (config.ARTIFACT_ID){
            console.log('Getting the asset')
            const artifact = await getAsset(config.API_KEY, config.ARTIFACT_ID);
            assetData = artifact.value
            console.log('Asset:', artifact.value)
            return artifact.value;
        } else {
            console.log('Creating the asset')
            const pendingArtifact = await createAsset(config.API_KEY, {
                name: config.ARTIFACT_NAME,
                author: config.ARTIFACT_AUTHOR
            });
            transactions.ASSET_TRANSACTION = pendingArtifact.value.transactionId;
            console.log('Please, confirm the asset creation transaction');
            console.log('Enter [x] to back to the menu');
            const isConfirmed = await checkTransactionStatus(config.API_KEY, pendingArtifact.value.transactionId);
            if (isConfirmed) {
                const artifact = await getAsset(config.API_KEY, pendingArtifact.value.id);
                assetData = artifact.value
                config.ARTIFACT_ID = artifact.value.id;
                return artifact;
            }
        }
    } catch (error) {
        console.error('FAILED TO CREATE OR GET ARTIFACT')
        error.message ? console.error(error.message) : null;
        console.error(`status: ${error.status}`);
    }
}

async function linkArtifacts(config, transactions){
    try {
        const payload = {
            targetAssetIds: [config.LINK_ARTIFACT_ID]
        }
        console.log('Linking assets')
        const transaction = await linkAssets(config.API_KEY, config.ARTIFACT_ID, payload)
        console.log('Please, confirm the link transaction')
        transaction.LINK_TRANSACTION = transaction.value.id
        const isConfirmed = await checkTransactionStatus(config.API_KEY, transaction.value.id)
        if (isConfirmed) {
            const asset = await getAsset(config.API_KEY, assetData.id, true)
            assetData = asset.value;
            return true
        }
    } catch (e) {
        console.error('FAILED TO LINK ASSETS')
        error.message ? console.error(error.message) : null;
        console.error(`status: ${error.status}`);
    }
}

async function createPokForArtifact(config){
    try {
        console.log('Creating PoK')
        const pok = await createPok(config.API_KEY, { address: config.ARTIFACT_AUTHOR});
        pokData.CHALLENGE_ID = pok.I;
        pokData.ADDRESS = config.ARTIFACT_AUTHOR;
        pokData.CHALLENGE = pok.C;
        console.log(pok);
        return pok;
    } catch (e) {
        console.error('Failed to create pok')
        console.error(e.message)
    }
}

async function getAsset(apiKey, assetId, details = true) {
    let url = `${appConfig.NOTARDEC_URL}/assets/${assetId}`;
    if (details) {
        url = `${appConfig.NOTARDEC_URL}/assets/${assetId}?details=true`;
    }
    const result = await axios.get(url, {
        headers: {
            'X-API-KEY': apiKey,
        },
    });
    return result.data;
}

async function createAsset(apiKey, payload) {
    const result = await axios.post(`${appConfig.NOTARDEC_URL}/assets`, payload, {
        headers: {
            'X-API-KEY': apiKey,
        },
    });
    return result.data;
}

async function getTransaction(apiKey, transactionId) {
    const result = await axios.get(`${appConfig.NOTARDEC_URL}/transactions/${transactionId}/status`, {
        headers: {
            'X-API-KEY': apiKey,
        },
    });
    return result.data;
}

async function linkAssets(apiKey, assetId, payload) {
    const result = await axios.post(`${appConfig.NOTARDEC_URL}/assets/${assetId}/links`, payload, {
        headers: {
            'X-API-KEY': apiKey,
        },
    });
    return result.data;
}

async function createPok(apiKey, payload) {
    const result = await axios.post(`${appConfig.NOTARDEC_URL}/pok`, payload, {
        headers: {
            'X-API-KEY': apiKey,
        },
    });
    return result.data;
}

async function respondPok(apiKey, payload) {
    const result = await axios.post(`${appConfig.NOTARDEC_URL}/pok/respond`, payload, {
        headers: {
            'X-API-KEY': apiKey,
        },
    });
    return result.data;
}

async function checkTransactionStatus(apiKey, transactionId) {
    const transactionStatus = await getTransaction(apiKey, transactionId);

    if (transactionStatus.value.state !== 'Confirmed') {
        console.log(`Transaction state: ${transactionStatus.value.state}`)
        await new Promise(resolve => setTimeout(resolve, 2000));
        return checkTransactionStatus(apiKey, transactionId);
    } else {
        console.log(`Transaction state: ${transactionStatus.value.state}`)
        return true;
    }
}

export const initWebSocketClient = (pokData) => {
    console.log('Connected to Notardec WS');
    const websocketListen= new WebSocket(appConfig.NOTARDEC_WS_URL);

    websocketListen.onclose = () => {
        console.log('Disconnected from Notardec WS');
        setTimeout(() => initWebSocketClient(), 200);
    };

    websocketListen.onerror = (error) => {
        console.log('There is an error with websockets' + error.message);
        websocketListen.close();
    };

    websocketListen.onmessage = (message) => {
        const data = JSON.parse(String(message.data));
        switch (data.type) {
            case 'POK_SUCCESS':
                const successPok = JSON.parse(data.message)
                pokData.POK_RESULT = successPok.result;
                pokData.CHALLENGE_ID = successPok.challengeId;
                pokData.ADDRESS = successPok.address
                console.log('Responded pok:', successPok)
                break;
            case 'POK_FAILED':
                const failedPok = JSON.parse(data.message)
                pokData.POK_RESULT = failedPok.result;
                pokData.CHALLENGE_ID = failedPok.challengeId;
                pokData.ADDRESS = failedPok.address
                console.log('Responded pok:', failedPok)
                break;
            case 'PING':
                websocketListen.send(JSON.stringify({ message: '', type: 'PONG' }));
        }
    };
};


async function displayMainMenu(config) {
    let choice;
    if(appConfig.STEP == '0'){
        console.log("This application demonstrates the functionality of PoK");
        console.log('Enter [1] to start the process')
        console.log("Enter [x] Exit");
        choice = await askForUserInput("Enter your choice: ");
    } else {
        console.log('Enter [1] Continue the process')
        if (appConfig.STEP >= 1) {
            console.log('Enter [2] Show the config')
        }
        if (appConfig.STEP >= 2) {
            console.log('Enter [3] Show the asset')
        }
        if (appConfig.STEP >= 5) {
            console.log('Enter [4] Show the pok')
        }
        console.log("Enter [x] Exit");
        choice = await askForUserInput("Enter your choice: ");
    }


    switch (choice) {
        case '1':
            switch (appConfig.STEP){
                case '0':
                    let isConfigCorrect = await settingUp(config)
                    if(isConfigCorrect == 0) {
                        console.error('Please, fix the config')
                        process.exit();
                    }
                    appConfig.STEP = '1'
                    break;
                case '1':
                    const asset = await createOrGetArtifact(config, transactions);
                    if(!asset) {
                        process.exit();
                    }
                    appConfig.STEP = '2'
                    break;
                case '2':
                    const link = await linkArtifacts(config, transactions);
                    if (!link){
                        process.exit();
                    }
                    appConfig.STEP = '3'
                    break;
                case '3':
                    const pok = await createPokForArtifact(config);
                    if(!pok){
                        process.exit();
                    }
                    appConfig.STEP = '4'
                    break;
                case '4':
                    console.log('Responding PoK')
                    const wallet = new Wallet(config.PRIVATE_KEY);
                    const signature = await wallet.signMessage(pokData.CHALLENGE);
                    const sign = await respondPok(config.API_KEY, {
                        address: config.ARTIFACT_AUTHOR,
                        signature: signature,
                        challengeId: pokData.CHALLENGE_ID
                    })
                    appConfig.STEP = '5'
                    break;
            }
            break;
        case '2':
            console.log('Config:')
            console.log(config)
            break;
        case '3':
            console.log('Asset:')
            console.log(assetData)
            break;
        case '4':
            console.log('Pok:')
            console.log(pokData);
            break;
        case 'x':
            process.exit(0); // Exit the application
            break;
        default:
            console.log("Invalid choice. Please try again.");
            break;
    }
    if ( appConfig.STEP == '5') {
        await askForUserInput("The process of creating a PoK is completed. Press enter to back to the menu: ");
    } else {
        await askForUserInput("Press enter to continue: ");
    }
    displayMainMenu(config)

}

async function askForUserInput(question) {
    return new Promise((resolve, reject) => {
        rl.question(question, (answer) => {
            resolve(answer);
        });
    });
}
async function start(){
    initWebSocketClient(pokData);
    displayMainMenu(config);
}

start()