[
  {
    "path": "README.md",
    "content": "# Nym Developer Tutorials Code\nRepo storing the code used in the Nym developer tutorials, which can be found on the [Developer Portal](https://nymtech.net/developers).\n\nThis repo contains the following:\n- `rust`\n\t- [`chain query service`](https://nymtech.net/developers/tutorials/cosmos-service/intro.html): part one of a two part tutorial on interacting with a Cosmos SDK blockchain via the Rust SDK. Build a service and use it to privately query the Sandbox testnet for account information in preparation of part two - creating a bandwidth credential.\n\n- `typescript`\n\t- [`simple service provider`](https://nymtech.net/developers/tutorials/simple-service-provider.html): see how the basic structure of a client and service passing messages through the mixnet can be set up.\n\nEach directory contains its own readme and instructions.\n"
  },
  {
    "path": "rust/chain-query-service/.gitignore",
    "content": "/target\n"
  },
  {
    "path": "rust/chain-query-service/Cargo.toml",
    "content": "[package]\nname = \"chain_query\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nclap = { version = \"4.0\", features = [\"derive\"] }\ncosmrs = \"=0.14.0\"\ntokio = { version = \"1.24.1\", features = [\"rt-multi-thread\", \"macros\"] }\nserde = \"1.0.152\"\nserde_json = \"1.0.91\"\nnym-sdk = { git = \"https://github.com/nymtech/nym\", branch = \"master\" }\nnym-sphinx-addressing = { git = \"https://github.com/nymtech/nym\", branch = \"master\" }\nnym-validator-client = { git = \"https://github.com/nymtech/nym\", branch = \"master\" }\nnym-bin-common = { git = \"https://github.com/nymtech/nym\", branch = \"master\" }\nnym-sphinx-anonymous-replies = { git = \"https://github.com/nymtech/nym\", branch = \"master\" }\nanyhow = \"1.0.72\"\n\n[[bin]]\nname = \"client\"\npath = \"bin/client.rs\"\n\n[[bin]]\nname = \"service\"\npath = \"bin/service.rs\"\n"
  },
  {
    "path": "rust/chain-query-service/README.md",
    "content": "Query the balance of account on Sandbox testnet blockchain through the mixnet with the Rust SDK.\n\n[//]: # (_Laying the groundwork for upcoming pt2: generating a bandwidth credential._)\npt2 additions: \n- change network to sandbox instead of mainnet \n- make it a bandwidth client \n- add request for bandwidth to client \n- send the token to service (log for now? use J's code to do _something_ with it?)\n\n## Usage\n```\n# console window #1\ncargo run --bin service\n\n# copy the service's Nym address from the terminal\n\n# console window #2\ncargo run --bin client query-balance n1lcutqz94k739s39u26rvexql40ehf42zd27fwe <SERVICE_ADDRESS_FROM_CLIPBOARD>\n```\n"
  },
  {
    "path": "rust/chain-query-service/bin/client.rs",
    "content": "use clap::{Args, Parser, Subcommand};\nuse chain_query::{client::query_balance, create_client};\nuse nym_sdk::mixnet::Recipient;\nuse nym_validator_client::nyxd::AccountId;\nuse nym_bin_common::logging::setup_logging; \n\n#[derive(Debug, Parser)]\n#[clap(name = \"rust sdk demo - chain query service\")]\n#[clap(about = \"query the sandbox testnet blockchain via the mixnet... part 2 coming soon\")]\nstruct Cli {\n    #[clap(subcommand)]\n    command: Option<Commands>,\n}\n\n#[derive(Debug, Subcommand)]\nenum Commands {\n    QueryBalance(QueryBalance),\n}\n\n#[derive(Debug, Args)]\nstruct QueryBalance {\n    /// the account we want to query\n    account: AccountId,\n    /// the address of the broadcaster service - this submits txs and queries the chain on our behalf\n    sp_address: String,\n}\n\n#[tokio::main]\nasync fn main() -> anyhow::Result<()> {\n    setup_logging();\n    let cli = Cli::parse();\n    let mut client = create_client(\"/tmp/client2\".into()).await;\n    let our_address = client.nym_address();\n    println!(\"\\nclient's nym address: {our_address}\");\n\n    match cli.command {\n        Some(Commands::QueryBalance(QueryBalance {\n            account,\n            sp_address,\n        })) => {\n            println!(\"\\nsending bank balance request to service via mixnet\\n\");\n            let sp_address = Recipient::try_from_base58_string(sp_address).unwrap();\n            let returned_balance = query_balance(account, &mut client, sp_address).await?;\n            println!(\"\\nreturned balance is: {}\", returned_balance);\n        }\n        None => {\n            println!(\"\\nno command specified - nothing to do\")\n        }\n    }\n    println!(\"\\ndisconnecting client\\n\");\n    client.disconnect().await;\n    println!(\"client disconnected\\n\");\n    Ok(())\n}\n"
  },
  {
    "path": "rust/chain-query-service/bin/service.rs",
    "content": "use chain_query::{\n    create_client, handle_request,\n    service::{create_broadcaster, get_balance},\n    BalanceResponse, RequestTypes, ResponseTypes,\n};\nuse nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag};\nuse nym_bin_common::logging::setup_logging;\nuse nym_sdk::mixnet::MixnetMessageSender;\n\n#[tokio::main]\nasync fn main() -> anyhow::Result<()> {\n    setup_logging();\n    let mut client = create_client(\"/tmp/service2\".into()).await;\n    let our_address = client.nym_address();\n    println!(\"\\nservice's nym address: {our_address}\");\n    // the httpclient we will use to broadcast our query to the blockchain\n    let broadcaster = create_broadcaster().await?;\n    println!(\"listening for messages, press CTRL-C to exit\\n\");\n\n    while let Some(received) = client.wait_for_messages().await {\n        for msg in received {\n            let request = match handle_request(msg) {\n                Ok(request) => request,\n                Err(err) => {\n                    eprintln!(\"failed to handle received request: {err}\");\n                    continue;\n                }\n            };\n\n            let return_recipient: AnonymousSenderTag = request.1.expect(\"no sender tag received\");\n            match request.0 {\n                RequestTypes::Balance(request) => {\n                    println!(\"\\nincoming balance request for: {}\\n\", request.account);\n\n                    let balance: BalanceResponse =\n                        get_balance(broadcaster.clone(), request.account).await?;\n\n                    let response = ResponseTypes::Balance(balance);\n\n                    println!(\"response from chain: {:#?}\", response);\n\n                    println!(\"\\nreturn recipient surb bucket: {}\", &return_recipient);\n                    println!(\"\\nsending response to {}\\n\", &return_recipient);\n                    // send response back to anon requesting client via mixnet\n                    let _ = client\n                        .send_reply(return_recipient, &serde_json::to_string(&response)?)\n                        .await;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "rust/chain-query-service/src/client.rs",
    "content": "use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_VALIDATOR_RPC};\nuse cosmrs::AccountId;\nuse nym_sdk::mixnet::MixnetClient;\nuse nym_sphinx_addressing::clients::Recipient;\nuse nym_sdk::mixnet::MixnetMessageSender;\nuse nym_validator_client::nyxd::Coin;\n\npub async fn query_balance(\n    account: AccountId,\n    client: &mut MixnetClient,\n    sp_address: Recipient,\n) -> anyhow::Result<Coin> {\n    // construct balance request\n    let message = RequestTypes::Balance(crate::BalanceRequest {\n        validator: DEFAULT_VALIDATOR_RPC.to_owned(), // rpc endpoint for broadcaster to use\n        account,\n    });\n\n    // send serialised request to service via mixnet\n    let _ = client\n        .send_message(sp_address, message.serialize(), Default::default())\n        .await;\n\n    let received = wait_for_non_empty_message(client).await?;\n\n    // listen for response from service\n    let sp_response = handle_response(received)?;\n\n    // match JSON -> ResponseType\n    let res = match sp_response {\n        crate::ResponseTypes::Balance(response) => {\n            println!(\"{:#?}\", response);\n            response.balance\n        }\n    };\n\n    Ok(res)\n}\n"
  },
  {
    "path": "rust/chain-query-service/src/lib.rs",
    "content": "use anyhow::bail;\nuse cosmrs::AccountId;\nuse nym_sdk::mixnet::{\n    AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths,\n};\nuse nym_validator_client::nyxd::Coin;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\npub mod client;\npub mod service;\n\npub const DEFAULT_VALIDATOR_RPC: &str = \"https://sandbox-validator1.nymtech.net\";\npub const DEFAULT_DENOM: &str = \"unym\";\npub const DEFAULT_PREFIX: &str = \"n\";\n\n#[derive(Debug, Deserialize, Serialize, PartialEq)]\npub struct BalanceRequest {\n    pub validator: String,\n    pub account: AccountId,\n}\n\n#[derive(Debug, Deserialize, Serialize, PartialEq)]\npub struct BalanceResponse {\n    pub balance: Coin,\n}\n\n#[derive(Debug, Deserialize, Serialize, PartialEq)]\npub enum RequestTypes {\n    Balance(BalanceRequest),\n}\n\nimpl RequestTypes {\n    pub fn serialize(&self) -> Vec<u8> {\n        serde_json::to_vec(self).expect(\"serde failure\")\n    }\n\n    pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {\n        serde_json::from_slice(raw.as_ref()).map_err(Into::into)\n    }\n}\n\n#[derive(Debug, Deserialize, Serialize, PartialEq)]\npub enum ResponseTypes {\n    Balance(BalanceResponse),\n}\n\nimpl ResponseTypes {\n    pub fn serialize(&self) -> Vec<u8> {\n        serde_json::to_vec(self).expect(\"serde failure\")\n    }\n\n    pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {\n        serde_json::from_slice(raw.as_ref()).map_err(Into::into)\n    }\n}\n\n// create our client with specified path for key storage\npub async fn create_client(config_path: PathBuf) -> MixnetClient {\n    let config_dir = config_path;\n    let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();\n    let client = MixnetClientBuilder::new_with_default_storage(storage_paths)\n        .await\n        .unwrap()\n        .build()\n        .unwrap();\n\n    client.connect_to_mixnet().await.unwrap()\n}\n\npub async fn wait_for_non_empty_message(\n    client: &mut MixnetClient,\n) -> anyhow::Result<ReconstructedMessage> {\n    while let Some(mut new_message) = client.wait_for_messages().await {\n        if !new_message.is_empty() {\n            return Ok(new_message.pop().unwrap());\n        }\n    }\n\n    bail!(\"did not receive any non-empty message\")\n}\n\npub fn handle_response(message: ReconstructedMessage) -> anyhow::Result<ResponseTypes> {\n    ResponseTypes::try_deserialize(message.message)\n}\n\npub fn handle_request(\n    message: ReconstructedMessage,\n) -> anyhow::Result<(RequestTypes, Option<AnonymousSenderTag>)> {\n    let request = RequestTypes::try_deserialize(message.message)?;\n    Ok((request, message.sender_tag))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn balance_response_serialization() {\n        let response = ResponseTypes::Balance(BalanceResponse {\n            balance: Coin::new(2399992158, \"unym\"),\n        });\n\n        let expected = b\"{\\\"Balance\\\":{\\\"balance\\\":{\\\"amount\\\":2399992158,\\\"denom\\\":\\\"unym\\\"}}}\";\n\n        assert_eq!(expected, response.serialize().as_slice())\n    }\n\n    #[test]\n    fn parsing_balance_response() {\n        let received = \"{\\\"Balance\\\":{\\\"balance\\\":{\\\"amount\\\":2399992158,\\\"denom\\\":\\\"unym\\\"}}}\";\n        let expected = ResponseTypes::Balance(BalanceResponse {\n            balance: Coin::new(2399992158, \"unym\"),\n        });\n\n        assert_eq!(expected, ResponseTypes::try_deserialize(received).unwrap())\n    }\n}\n"
  },
  {
    "path": "rust/chain-query-service/src/service.rs",
    "content": "use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC};\nuse cosmrs::rpc::HttpClient;\nuse cosmrs::AccountId;\nuse nym_validator_client::nyxd::{Coin, CosmWasmClient};\n\npub async fn create_broadcaster() -> anyhow::Result<HttpClient> {\n    let broadcaster: HttpClient = HttpClient::new(DEFAULT_VALIDATOR_RPC)?;\n    Ok(broadcaster)\n}\n\npub async fn get_balance(\n    broadcaster: HttpClient,\n    account: AccountId,\n) -> anyhow::Result<BalanceResponse> {\n    let balance = broadcaster\n        .get_balance(&account, DEFAULT_DENOM.to_string())\n        .await\n        .unwrap()\n        .unwrap();\n    Ok(BalanceResponse {\n        balance: Coin {\n            amount: balance.amount,\n            denom: balance.denom,\n        }, \n    })\n}\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/README.md",
    "content": "# Simple Service Provider \n\nThe code for the first Nym developer tutorial, building a simple Service Provider and some client-side code for sending messages through the mixnet. \n\nYou can find the tutorial [here](https://nymtech.net/developers/tutorials/simple-service-provider.html).\n\n## Setup \n* Each component requires a Nym Websocket Client to communicate with the mixnet. The User Client code listens for a websocket connection on port 1977, and the Service Provider on 1978. \n* Service Provider: \n```\ncd service-provider\nnpm install \nnpm run start:dev \n```\n* User Client: \n```\ncd user-client \nnpm install \nnpm start\n```\n\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/service-provider/.gitignore",
    "content": "*.cache\nnode_modules\n.DS_Store\n.DS_Store?\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/service-provider/README.md",
    "content": "Simple Service Provider using Nym Client Websocket - Service Provider Code\n\nSetup the project using 'npm install'\n\nRun the application using 'npm run start:dev'\n\nnym-client Quickstart:\n\nAfter following https://nymtech.net/docs/binaries/building-nym.html , navigate to `target/release` within the `nym` folder and execute the following in your terminal:\n\n```\n./nym-client init --id service-provider --port 1978\n./nym-client run --id service-provider\n\n```\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/service-provider/nodemon.json",
    "content": "{\n    \"watch\": [\n        \"src\"\n    ],\n    \"ext\": \".ts,.js\",\n    \"ignore\": [],\n    \"exec\": \"ts-node ./src/index.ts\"\n}"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/service-provider/package.json",
    "content": "{\n  \"name\": \"service-provider\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start:dev\": \"nodemon\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^18.14.0\",\n    \"@types/ws\": \"^8.5.4\",\n    \"nodemon\": \"^2.0.20\",\n    \"ts-node\": \"^10.9.1\",\n    \"typescript\": \"^4.8.4\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"ws\": \"^8.12.0\"\n  }\n}\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/service-provider/src/index.ts",
    "content": "import WebSocket, { MessageEvent } from \"ws\";\n\nvar ourAddress:          string;\nvar websocketConnection: any;\n\nasync function main() {\n    var port = '1978' \n    var localClientUrl = \"ws://127.0.0.1:\" + port;\n\n    // Set up and handle websocket connection to our desktop client.\n    websocketConnection = await connectWebsocket(localClientUrl).then(function (c) {\n        return c;\n    }).catch(function (err) {\n        console.log(\"Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port \" + port + \"?\");\n        console.log(err);\n    })\n\n    websocketConnection.onmessage = function (e : any) {\n        handleResponse(e);\n    };\n\n    sendSelfAddressRequest();\n}\n\n// Handle any messages that come back down the websocket. \nfunction handleResponse(responseMessageEvent : MessageEvent) {\n\n    try {\n            let response = JSON.parse(responseMessageEvent.data.toString());\n        if (response.type == \"error\") {\n            console.log(\"\\x1b[91mAn error occured: \" + response.message + \"\\x1b[0m\")\n        } else if (response.type == \"selfAddress\") {\n            ourAddress = response.address;\n            console.log(\"\\x1b[94mOur address is: \" + ourAddress + \"\\x1b[0m\")\n        } else if (response.type == \"received\") {\n            let messageContent = JSON.parse(response.message)\n\n            console.log('\\x1b[93mRecieved : \\x1b[0m');\n            console.log('\\x1b[92mName : ' + messageContent.name + '\\x1b[0m');\n            console.log('\\x1b[92mComment : ' + messageContent.comment + '\\x1b[0m');\n\n            console.log('\\x1b[93mSending response back to client... \\x1b[0m')\n\n\t    sendMessageToMixnet(response.senderTag)\n        }\n    } catch (_) {\n        console.log('something went wrong in handleResponse')\n    }\n}\n\nfunction sendMessageToMixnet(senderTag: string) {\n\n    // Place each of the form values into a single object to be sent.\n    const messageContentToSend = {\n        text: 'We recieved your request - this reply sent to you anonymously with SURBs',\n        fromAddress : ourAddress\n    }\n    \n    const message = {\n        type: \"reply\",\n        message: JSON.stringify(messageContentToSend),\n    \tsenderTag: senderTag\n    }\n    \n    // Send our message object via out via our websocket connection.\n    websocketConnection.send(JSON.stringify(message));\n}\n\n// Send a message to the mixnet client, asking what our own address is. \nfunction sendSelfAddressRequest() {\n    var selfAddress = {\n        type: \"selfAddress\"\n    }\n    websocketConnection.send(JSON.stringify(selfAddress));\n}\n\n// Function that connects our application to the mixnet Websocket. We want to call this first in our main function.\nfunction connectWebsocket(url : string) {\n    return new Promise(function (resolve, reject) {\n        var server = new WebSocket(url);\n        console.log('connecting to Mixnet Websocket (Nym Client)...')\n        server.onopen = function () {\n            resolve(server);\n        };\n        server.onerror = function (err) {\n            reject(err);\n        };\n\n    });\n}\n\nmain();\n\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/service-provider/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n      \"target\": \"es2017\", \n      \"lib\": [\n        \"es6\"\n      ],\n      \"module\": \"Node16\", \n      \"rootDir\": \"src\", \n      \"resolveJsonModule\": true, \n      \"allowJs\": true,                      \n      \"outDir\": \"build\", \n      \"esModuleInterop\": true, \n      \"forceConsistentCasingInFileNames\": true, \n      \"strict\": true, \n      \"noImplicitAny\": true, \n      \"skipLibCheck\": true \n    }\n  }"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/.gitignore",
    "content": "*.cache\nnode_modules\n.DS_Store\n.DS_Store?\n/dist\n.cache"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/README.md",
    "content": "Simple Service Provider using Nym Client Websocket - User Client Code\n\nSetup the project using 'npm install'\n\nRun the application using 'npm start'\n\nThe default browser location youll find the application will be:\n http://localhost:1234/\n \nPopulate `targetAddress` in `index.ts` with the address that is provider by the service-providers instance of `nym-client`\n\nnym-client Quickstart:\n\nAfter following https://nymtech.net/docs/binaries/building-nym.html , navigate to `target/release` within the `nym` folder and execute the following in your terminal:\n\n```\n./nym-client init --id user-client\n./nym-client run --id user-client\n\n```\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/assets/styles.css",
    "content": ":host {\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica,\n        Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n    font-size: 14px;\n    color: #333;\n    box-sizing: border-box;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n}\n\nbody{\n    background: #111627;\n    font-family: sans-serif;\n}\n    \nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    margin: 8px 0;\n    color: white;\n}\n\np {\n    margin: 0;\n}\n\n.form-field-label{\n    color: white;\n}\n\n/* Text Inputs */\nselect{\n    height:45px;\n    background:#ffffff;\n    border:4px solid #F4511E;\n    border-radius:4px;\n    font-size:18px;\n    padding-left:8px;\n    margin-bottom: 0.5rem;\n}\n\ninput:focus{\n    border:4px solid #F4511E;\n    outline:none;\n    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);\n}\n\n/* Text Inputs */\ninput[type=\"text\"]{\n    width:400px;\n    height:45px;\n    background:#ffffff;\n    border:4px solid #F4511E;\n    border-radius:4px;\n    font-size:18px;\n    padding-left:8px;\n    margin-bottom: 0.5rem;\n}\ninput[type=\"text\"]:focus{\n    border:4px solid #F4511E;\n    outline:none;\n    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);\n}\n/* Number Inputs */\ninput[type=\"number\"]{\n    width:400px;\n    height:45px;\n    background:#ffffff;\n    border:4px solid #F4511E;\n    border-radius:4px;\n    font-size:18px;\n    padding-left:8px;\n    margin-bottom: 0.5rem;\n}\n\ninput[type=\"number\"]:focus{\n    border:4px solid #F4511E;\n    outline:none;\n    box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);\n}\n\n#get-account-button{\n    margin-bottom: 1rem;\n}\n\n#send-button{\n    margin-bottom: 1rem;\n}\n\n\n.toolbar {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    height: 60px;\n    display: flex;\n    align-items: center;\n    background-color: #111627;\n    color: white;\n    font-weight: 600;\n    margin-bottom: 2rem;\n}\n\n.submit-button {\n    box-shadow: 0 1.5px 4px rgba(0, 0, 0, 0.24), 0 1.5px 6px rgba(0, 0, 0, 0.12);\n    background:linear-gradient(90deg, #f4731b 1.05%, #f12d50 100%);\n    border: 1px solid #F4511E;\n    border-radius: 4px;\n    cursor: pointer;\n    color: #fff;\n    display: inline-block;\n    float: left;\n    letter-spacing:2px;\n    padding:10px;\n    margin: auto;\n}\n\n.content {\n    display: flex;\n    margin: 82px auto 32px;\n    padding: 0 16px;\n    max-width: 960px;\n    flex-direction: column;\n    align-items: center;\n}\n\n.section-container {\n    display: grid;\n    flex-wrap: wrap;\n    justify-content: center;\n    margin-top: 16px;\n    margin-top: 1rem;\n    margin-bottom: 1rem;\n    width: 850px;\n}\n\n.output-container{\n    margin-left: 20px;\n    margin-right: 20px;\n    margin-left:20px;\n    max-width: fit-content;\n    color: #fff;\n    background: #333;\n    padding-left: 5px;\n    padding-right: 5px;\n}\n\n.toolbar{\n    margin-left: 20px;;\n}"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/package.json",
    "content": "{\n  \"name\": \"user-client\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"parcel src/index.html\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"ts-node\": \"^10.9.1\",\n    \"typescript\": \"^4.9.4\"\n  }\n}\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/src/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\">\n        <title>Mixnet Websocket Starter Client</title>\n        <link rel=\"stylesheet\" href=\"../assets/styles.css\"/>\n    </head>\n    <body>\n        <div class=\"content\" role=\"main\">\n            <div class=\"toolbar\">\n                <h3>Mixnet Websocket Starter User Client</h3>\n            </div>\n            \n            <div class=\"section-container\">\n               \n                <label for=\"nameInput\" class=\"form-field-label\">Moniker</label>\n                <input id=\"nameInput\" type=\"text\" value=\"An0n\" name=\"nameInput\">\n\n                <label for=\"textInput\" class=\"form-field-label\">Comment</label>\n                <input id=\"textInput\" type=\"text\" value=\"I would like to use your private service\" name=\"textInput\">\n         \n                <div id=\"send-button\">\n                    <label for=\"send-button\" class=\"submit-button\">Send</label>\n                </div>\n            </div>\n        </div>\n        \n        <div class=\"\" style=\"margin-left:20px;max-width: fit-content;\">\n            <div style=\"color: white;margin-bottom: 2rem;\">\n                <h4>How it works</h4>\n                <p>Once you have started your Nym Websocket client, you can fill out the form and send data to the Service Provider via mixnet using the <b>\"Send\"</b> button.</p>\n                <p>Below, you can see the activity log. <b style='color: #36d481;'>Sent</b> messages will display in <b style='color: #36d481;'>green</b> while <b style='color: orange;'>received</b> messages will display in <b style='color: orange;'>orange</b>.</p>\n            </div>\n        </div>\n        \n        <h3 style=\"margin-left:10px\">Activity Log</h3>\n        \n        <p class=\"output-container\">\n            <span id=\"output\"></div>\n        </p>\n        <script src=\"index.ts\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/src/index.ts",
    "content": "// The address that is given to us from our mixnet client.\nvar ourAddress: string;\n\n// Address we want to send our messages to. Replace it with the address of your Service Provider's Nym client!\nvar targetAddress: string = '6V5eEguz4rUsfntVLKQuD2ymgdY5iDKCV2GY2EH3CxG4.AKdk22atwRaVkN2PLEDsWUKKDc3ieNm1avKqVGgmJx8s@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE';\n\n// Variable that holds our websocket connection data.\nvar websocketConnection: any;\n\nasync function main() {\n    var port = '1977' // Nym Websocket Client listens on 1977 by default.\n    var localClientUrl = \"ws://127.0.0.1:\" + port;\n    \n    // Set up and handle websocket connection to our desktop client.\n    websocketConnection = await connectWebsocket(localClientUrl).then(function (c) {\n        return c;\n    }).catch(function (err) {\n        displayClientMessage(\"Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port \" + port + \"?\");\n    })\n\n    websocketConnection.onmessage = function (e) {\n        handleResponse(e);\n    };\n    \n    sendSelfAddressRequest();\n    \n    // Set up the send button\n    const sendButton = document.querySelector('#send-button');\n    \n    sendButton?.addEventListener('click', function handleClick(event) {\n        sendMessageToMixnet(); \n    });\n}\n\n// Get our own client address to log in the activity log so we know what our address is in the mixnet via our application UI\nfunction sendSelfAddressRequest() {\n    var selfAddress = {\n        type: \"selfAddress\"\n    }\n    displayJsonSend(selfAddress);\n    websocketConnection.send(JSON.stringify(selfAddress));\n}\n\n// Function that gets the form data and sends that to the mixnet in a stringified JSON format.\nfunction sendMessageToMixnet() {\n\n    // Access our form's elements current values\n    var nameInput = (<HTMLInputElement>document.getElementById(\"nameInput\")).value;\n    var textInput = (<HTMLInputElement>document.getElementById(\"textInput\")).value;\n   \n    // construct the content of our message to send through the mixnet \n    const messageContentToSend = {\n        name : nameInput,\n        comment : textInput,\n    }\n    \n    // construct our message object to send to the SP via the mixnet   \n    const message = {\n        type: \"sendAnonymous\",\n        message: JSON.stringify(messageContentToSend),\n        recipient: targetAddress,\n        replySurbs: 5\n    }\n    \n    // Display the json data you're sending to the SP on the UI\n    displayJsonSend(message);\n    \n    // Send our message object via out via our websocket connection.\n    websocketConnection.send(JSON.stringify(message));\n}\n\n// Display responses into our activity log.\nfunction displayJsonSend(message) {\n    let sendDiv = document.createElement(\"div\")\n    let paragraph = document.createElement(\"p\")\n    paragraph.setAttribute('style', 'color: #36d481')\n    let paragraphContent = document.createTextNode(\"sent >>> \" + JSON.stringify(message))\n    paragraph.appendChild(paragraphContent)\n    \n    sendDiv.appendChild(paragraph)\n    document.getElementById(\"output\").appendChild(sendDiv)\n}\n\n// Connect to a websocket. \nfunction connectWebsocket(url) {\n    return new Promise(function (resolve, reject) {\n        var server = new WebSocket(url);\n        console.log('connecting to Websocket Server (Nym Client)...')\n        server.onopen = function () {\n            resolve(server);\n        };\n        server.onerror = function (err) {\n            reject(err);\n        };\n    });\n}\n\n// Display messages that relate to initialising our client + client status in our activity log.\nfunction displayClientMessage(message) {\n    document.getElementById(\"output\").innerHTML += \"<p>\" + message + \"</p >\";\n}\n\n// Handle any messages that come back down the websocket.\nfunction handleResponse(resp) {\n    try {\n        let response = JSON.parse(resp.data);\n        if (response.type == \"error\") {\n            displayJsonResponse(\"Server responded with error: \" + response.message);\n        } else if (response.type == \"selfAddress\") {\n            ourAddress = response.address;\n            displayClientMessage(\"Our address is:  \" + ourAddress);\n        } else if (response.type == \"received\") {\n            handleReceivedTextMessage(response)\n        }\n    } catch (_) {\n        displayJsonResponse(resp.data)\n    }\n}\n\n// Handle any string message values that are received through messages sent back to us.\nfunction handleReceivedTextMessage(message) {\n    const text = JSON.parse(message.message)\n    displayJsonResponse(text)\n}\n\n// Display websocket responses in the Activity Log.\nfunction displayJsonResponse(message) {\n    let receivedDiv = document.createElement(\"div\")\n    let paragraph = document.createElement(\"p\")\n    paragraph.setAttribute('style', 'color: orange')\n    let textNode = document.createTextNode(\"received >>> \" + message.text)\n    paragraph.appendChild(textNode)\n    \n    receivedDiv.appendChild(paragraph)\n    document.getElementById(\"output\").appendChild(receivedDiv)\n}\n\nmain();\n"
  },
  {
    "path": "typescript/simple-service-provider-tutorial/user-client/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"esModuleInterop\": true,\n        \"target\": \"es6\",\n        \"moduleResolution\": \"node\",\n        \"sourceMap\": true,\n        \"outDir\": \"dist\"\n    },\n    \"lib\": [\"es2015\"]\n}"
  }
]