Repository: nymtech/developer-tutorials
Branch: main
Commit: 0130ee5a61cd
Files: 23
Total size: 26.9 KB
Directory structure:
gitextract_datox1l_/
├── README.md
├── rust/
│ └── chain-query-service/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── bin/
│ │ ├── client.rs
│ │ └── service.rs
│ └── src/
│ ├── client.rs
│ ├── lib.rs
│ └── service.rs
└── typescript/
└── simple-service-provider-tutorial/
├── README.md
├── service-provider/
│ ├── .gitignore
│ ├── README.md
│ ├── nodemon.json
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ └── tsconfig.json
└── user-client/
├── .gitignore
├── README.md
├── assets/
│ └── styles.css
├── package.json
├── src/
│ ├── index.html
│ └── index.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# Nym Developer Tutorials Code
Repo storing the code used in the Nym developer tutorials, which can be found on the [Developer Portal](https://nymtech.net/developers).
This repo contains the following:
- `rust`
- [`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.
- `typescript`
- [`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.
Each directory contains its own readme and instructions.
================================================
FILE: rust/chain-query-service/.gitignore
================================================
/target
================================================
FILE: rust/chain-query-service/Cargo.toml
================================================
[package]
name = "chain_query"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0", features = ["derive"] }
cosmrs = "=0.14.0"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
serde = "1.0.152"
serde_json = "1.0.91"
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" }
anyhow = "1.0.72"
[[bin]]
name = "client"
path = "bin/client.rs"
[[bin]]
name = "service"
path = "bin/service.rs"
================================================
FILE: rust/chain-query-service/README.md
================================================
Query the balance of account on Sandbox testnet blockchain through the mixnet with the Rust SDK.
[//]: # (_Laying the groundwork for upcoming pt2: generating a bandwidth credential._)
pt2 additions:
- change network to sandbox instead of mainnet
- make it a bandwidth client
- add request for bandwidth to client
- send the token to service (log for now? use J's code to do _something_ with it?)
## Usage
```
# console window #1
cargo run --bin service
# copy the service's Nym address from the terminal
# console window #2
cargo run --bin client query-balance n1lcutqz94k739s39u26rvexql40ehf42zd27fwe <SERVICE_ADDRESS_FROM_CLIPBOARD>
```
================================================
FILE: rust/chain-query-service/bin/client.rs
================================================
use clap::{Args, Parser, Subcommand};
use chain_query::{client::query_balance, create_client};
use nym_sdk::mixnet::Recipient;
use nym_validator_client::nyxd::AccountId;
use nym_bin_common::logging::setup_logging;
#[derive(Debug, Parser)]
#[clap(name = "rust sdk demo - chain query service")]
#[clap(about = "query the sandbox testnet blockchain via the mixnet... part 2 coming soon")]
struct Cli {
#[clap(subcommand)]
command: Option<Commands>,
}
#[derive(Debug, Subcommand)]
enum Commands {
QueryBalance(QueryBalance),
}
#[derive(Debug, Args)]
struct QueryBalance {
/// the account we want to query
account: AccountId,
/// the address of the broadcaster service - this submits txs and queries the chain on our behalf
sp_address: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let cli = Cli::parse();
let mut client = create_client("/tmp/client2".into()).await;
let our_address = client.nym_address();
println!("\nclient's nym address: {our_address}");
match cli.command {
Some(Commands::QueryBalance(QueryBalance {
account,
sp_address,
})) => {
println!("\nsending bank balance request to service via mixnet\n");
let sp_address = Recipient::try_from_base58_string(sp_address).unwrap();
let returned_balance = query_balance(account, &mut client, sp_address).await?;
println!("\nreturned balance is: {}", returned_balance);
}
None => {
println!("\nno command specified - nothing to do")
}
}
println!("\ndisconnecting client\n");
client.disconnect().await;
println!("client disconnected\n");
Ok(())
}
================================================
FILE: rust/chain-query-service/bin/service.rs
================================================
use chain_query::{
create_client, handle_request,
service::{create_broadcaster, get_balance},
BalanceResponse, RequestTypes, ResponseTypes,
};
use nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag};
use nym_bin_common::logging::setup_logging;
use nym_sdk::mixnet::MixnetMessageSender;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let mut client = create_client("/tmp/service2".into()).await;
let our_address = client.nym_address();
println!("\nservice's nym address: {our_address}");
// the httpclient we will use to broadcast our query to the blockchain
let broadcaster = create_broadcaster().await?;
println!("listening for messages, press CTRL-C to exit\n");
while let Some(received) = client.wait_for_messages().await {
for msg in received {
let request = match handle_request(msg) {
Ok(request) => request,
Err(err) => {
eprintln!("failed to handle received request: {err}");
continue;
}
};
let return_recipient: AnonymousSenderTag = request.1.expect("no sender tag received");
match request.0 {
RequestTypes::Balance(request) => {
println!("\nincoming balance request for: {}\n", request.account);
let balance: BalanceResponse =
get_balance(broadcaster.clone(), request.account).await?;
let response = ResponseTypes::Balance(balance);
println!("response from chain: {:#?}", response);
println!("\nreturn recipient surb bucket: {}", &return_recipient);
println!("\nsending response to {}\n", &return_recipient);
// send response back to anon requesting client via mixnet
let _ = client
.send_reply(return_recipient, &serde_json::to_string(&response)?)
.await;
}
}
}
}
Ok(())
}
================================================
FILE: rust/chain-query-service/src/client.rs
================================================
use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_VALIDATOR_RPC};
use cosmrs::AccountId;
use nym_sdk::mixnet::MixnetClient;
use nym_sphinx_addressing::clients::Recipient;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_validator_client::nyxd::Coin;
pub async fn query_balance(
account: AccountId,
client: &mut MixnetClient,
sp_address: Recipient,
) -> anyhow::Result<Coin> {
// construct balance request
let message = RequestTypes::Balance(crate::BalanceRequest {
validator: DEFAULT_VALIDATOR_RPC.to_owned(), // rpc endpoint for broadcaster to use
account,
});
// send serialised request to service via mixnet
let _ = client
.send_message(sp_address, message.serialize(), Default::default())
.await;
let received = wait_for_non_empty_message(client).await?;
// listen for response from service
let sp_response = handle_response(received)?;
// match JSON -> ResponseType
let res = match sp_response {
crate::ResponseTypes::Balance(response) => {
println!("{:#?}", response);
response.balance
}
};
Ok(res)
}
================================================
FILE: rust/chain-query-service/src/lib.rs
================================================
use anyhow::bail;
use cosmrs::AccountId;
use nym_sdk::mixnet::{
AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths,
};
use nym_validator_client::nyxd::Coin;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub mod client;
pub mod service;
pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymtech.net";
pub const DEFAULT_DENOM: &str = "unym";
pub const DEFAULT_PREFIX: &str = "n";
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct BalanceRequest {
pub validator: String,
pub account: AccountId,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct BalanceResponse {
pub balance: Coin,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub enum RequestTypes {
Balance(BalanceRequest),
}
impl RequestTypes {
pub fn serialize(&self) -> Vec<u8> {
serde_json::to_vec(self).expect("serde failure")
}
pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {
serde_json::from_slice(raw.as_ref()).map_err(Into::into)
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub enum ResponseTypes {
Balance(BalanceResponse),
}
impl ResponseTypes {
pub fn serialize(&self) -> Vec<u8> {
serde_json::to_vec(self).expect("serde failure")
}
pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {
serde_json::from_slice(raw.as_ref()).map_err(Into::into)
}
}
// create our client with specified path for key storage
pub async fn create_client(config_path: PathBuf) -> MixnetClient {
let config_dir = config_path;
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
.await
.unwrap()
.build()
.unwrap();
client.connect_to_mixnet().await.unwrap()
}
pub async fn wait_for_non_empty_message(
client: &mut MixnetClient,
) -> anyhow::Result<ReconstructedMessage> {
while let Some(mut new_message) = client.wait_for_messages().await {
if !new_message.is_empty() {
return Ok(new_message.pop().unwrap());
}
}
bail!("did not receive any non-empty message")
}
pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result<ResponseTypes> {
ResponseTypes::try_deserialize(message.message)
}
pub fn handle_request(
message: ReconstructedMessage,
) -> anyhow::Result<(RequestTypes, Option<AnonymousSenderTag>)> {
let request = RequestTypes::try_deserialize(message.message)?;
Ok((request, message.sender_tag))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn balance_response_serialization() {
let response = ResponseTypes::Balance(BalanceResponse {
balance: Coin::new(2399992158, "unym"),
});
let expected = b"{\"Balance\":{\"balance\":{\"amount\":2399992158,\"denom\":\"unym\"}}}";
assert_eq!(expected, response.serialize().as_slice())
}
#[test]
fn parsing_balance_response() {
let received = "{\"Balance\":{\"balance\":{\"amount\":2399992158,\"denom\":\"unym\"}}}";
let expected = ResponseTypes::Balance(BalanceResponse {
balance: Coin::new(2399992158, "unym"),
});
assert_eq!(expected, ResponseTypes::try_deserialize(received).unwrap())
}
}
================================================
FILE: rust/chain-query-service/src/service.rs
================================================
use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC};
use cosmrs::rpc::HttpClient;
use cosmrs::AccountId;
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
pub async fn create_broadcaster() -> anyhow::Result<HttpClient> {
let broadcaster: HttpClient = HttpClient::new(DEFAULT_VALIDATOR_RPC)?;
Ok(broadcaster)
}
pub async fn get_balance(
broadcaster: HttpClient,
account: AccountId,
) -> anyhow::Result<BalanceResponse> {
let balance = broadcaster
.get_balance(&account, DEFAULT_DENOM.to_string())
.await
.unwrap()
.unwrap();
Ok(BalanceResponse {
balance: Coin {
amount: balance.amount,
denom: balance.denom,
},
})
}
================================================
FILE: typescript/simple-service-provider-tutorial/README.md
================================================
# Simple Service Provider
The code for the first Nym developer tutorial, building a simple Service Provider and some client-side code for sending messages through the mixnet.
You can find the tutorial [here](https://nymtech.net/developers/tutorials/simple-service-provider.html).
## Setup
* 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.
* Service Provider:
```
cd service-provider
npm install
npm run start:dev
```
* User Client:
```
cd user-client
npm install
npm start
```
================================================
FILE: typescript/simple-service-provider-tutorial/service-provider/.gitignore
================================================
*.cache
node_modules
.DS_Store
.DS_Store?
================================================
FILE: typescript/simple-service-provider-tutorial/service-provider/README.md
================================================
Simple Service Provider using Nym Client Websocket - Service Provider Code
Setup the project using 'npm install'
Run the application using 'npm run start:dev'
nym-client Quickstart:
After following https://nymtech.net/docs/binaries/building-nym.html , navigate to `target/release` within the `nym` folder and execute the following in your terminal:
```
./nym-client init --id service-provider --port 1978
./nym-client run --id service-provider
```
================================================
FILE: typescript/simple-service-provider-tutorial/service-provider/nodemon.json
================================================
{
"watch": [
"src"
],
"ext": ".ts,.js",
"ignore": [],
"exec": "ts-node ./src/index.ts"
}
================================================
FILE: typescript/simple-service-provider-tutorial/service-provider/package.json
================================================
{
"name": "service-provider",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start:dev": "nodemon",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@types/node": "^18.14.0",
"@types/ws": "^8.5.4",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
},
"author": "",
"license": "ISC",
"dependencies": {
"ws": "^8.12.0"
}
}
================================================
FILE: typescript/simple-service-provider-tutorial/service-provider/src/index.ts
================================================
import WebSocket, { MessageEvent } from "ws";
var ourAddress: string;
var websocketConnection: any;
async function main() {
var port = '1978'
var localClientUrl = "ws://127.0.0.1:" + port;
// Set up and handle websocket connection to our desktop client.
websocketConnection = await connectWebsocket(localClientUrl).then(function (c) {
return c;
}).catch(function (err) {
console.log("Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port " + port + "?");
console.log(err);
})
websocketConnection.onmessage = function (e : any) {
handleResponse(e);
};
sendSelfAddressRequest();
}
// Handle any messages that come back down the websocket.
function handleResponse(responseMessageEvent : MessageEvent) {
try {
let response = JSON.parse(responseMessageEvent.data.toString());
if (response.type == "error") {
console.log("\x1b[91mAn error occured: " + response.message + "\x1b[0m")
} else if (response.type == "selfAddress") {
ourAddress = response.address;
console.log("\x1b[94mOur address is: " + ourAddress + "\x1b[0m")
} else if (response.type == "received") {
let messageContent = JSON.parse(response.message)
console.log('\x1b[93mRecieved : \x1b[0m');
console.log('\x1b[92mName : ' + messageContent.name + '\x1b[0m');
console.log('\x1b[92mComment : ' + messageContent.comment + '\x1b[0m');
console.log('\x1b[93mSending response back to client... \x1b[0m')
sendMessageToMixnet(response.senderTag)
}
} catch (_) {
console.log('something went wrong in handleResponse')
}
}
function sendMessageToMixnet(senderTag: string) {
// Place each of the form values into a single object to be sent.
const messageContentToSend = {
text: 'We recieved your request - this reply sent to you anonymously with SURBs',
fromAddress : ourAddress
}
const message = {
type: "reply",
message: JSON.stringify(messageContentToSend),
senderTag: senderTag
}
// Send our message object via out via our websocket connection.
websocketConnection.send(JSON.stringify(message));
}
// Send a message to the mixnet client, asking what our own address is.
function sendSelfAddressRequest() {
var selfAddress = {
type: "selfAddress"
}
websocketConnection.send(JSON.stringify(selfAddress));
}
// Function that connects our application to the mixnet Websocket. We want to call this first in our main function.
function connectWebsocket(url : string) {
return new Promise(function (resolve, reject) {
var server = new WebSocket(url);
console.log('connecting to Mixnet Websocket (Nym Client)...')
server.onopen = function () {
resolve(server);
};
server.onerror = function (err) {
reject(err);
};
});
}
main();
================================================
FILE: typescript/simple-service-provider-tutorial/service-provider/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es2017",
"lib": [
"es6"
],
"module": "Node16",
"rootDir": "src",
"resolveJsonModule": true,
"allowJs": true,
"outDir": "build",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
}
}
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/.gitignore
================================================
*.cache
node_modules
.DS_Store
.DS_Store?
/dist
.cache
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/README.md
================================================
Simple Service Provider using Nym Client Websocket - User Client Code
Setup the project using 'npm install'
Run the application using 'npm start'
The default browser location youll find the application will be:
http://localhost:1234/
Populate `targetAddress` in `index.ts` with the address that is provider by the service-providers instance of `nym-client`
nym-client Quickstart:
After following https://nymtech.net/docs/binaries/building-nym.html , navigate to `target/release` within the `nym` folder and execute the following in your terminal:
```
./nym-client init --id user-client
./nym-client run --id user-client
```
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/assets/styles.css
================================================
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body{
background: #111627;
font-family: sans-serif;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
color: white;
}
p {
margin: 0;
}
.form-field-label{
color: white;
}
/* Text Inputs */
select{
height:45px;
background:#ffffff;
border:4px solid #F4511E;
border-radius:4px;
font-size:18px;
padding-left:8px;
margin-bottom: 0.5rem;
}
input:focus{
border:4px solid #F4511E;
outline:none;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}
/* Text Inputs */
input[type="text"]{
width:400px;
height:45px;
background:#ffffff;
border:4px solid #F4511E;
border-radius:4px;
font-size:18px;
padding-left:8px;
margin-bottom: 0.5rem;
}
input[type="text"]:focus{
border:4px solid #F4511E;
outline:none;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}
/* Number Inputs */
input[type="number"]{
width:400px;
height:45px;
background:#ffffff;
border:4px solid #F4511E;
border-radius:4px;
font-size:18px;
padding-left:8px;
margin-bottom: 0.5rem;
}
input[type="number"]:focus{
border:4px solid #F4511E;
outline:none;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
}
#get-account-button{
margin-bottom: 1rem;
}
#send-button{
margin-bottom: 1rem;
}
.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #111627;
color: white;
font-weight: 600;
margin-bottom: 2rem;
}
.submit-button {
box-shadow: 0 1.5px 4px rgba(0, 0, 0, 0.24), 0 1.5px 6px rgba(0, 0, 0, 0.12);
background:linear-gradient(90deg, #f4731b 1.05%, #f12d50 100%);
border: 1px solid #F4511E;
border-radius: 4px;
cursor: pointer;
color: #fff;
display: inline-block;
float: left;
letter-spacing:2px;
padding:10px;
margin: auto;
}
.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}
.section-container {
display: grid;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
margin-top: 1rem;
margin-bottom: 1rem;
width: 850px;
}
.output-container{
margin-left: 20px;
margin-right: 20px;
margin-left:20px;
max-width: fit-content;
color: #fff;
background: #333;
padding-left: 5px;
padding-right: 5px;
}
.toolbar{
margin-left: 20px;;
}
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/package.json
================================================
{
"name": "user-client",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "parcel src/index.html",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
}
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/src/index.html
================================================
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Mixnet Websocket Starter Client</title>
<link rel="stylesheet" href="../assets/styles.css"/>
</head>
<body>
<div class="content" role="main">
<div class="toolbar">
<h3>Mixnet Websocket Starter User Client</h3>
</div>
<div class="section-container">
<label for="nameInput" class="form-field-label">Moniker</label>
<input id="nameInput" type="text" value="An0n" name="nameInput">
<label for="textInput" class="form-field-label">Comment</label>
<input id="textInput" type="text" value="I would like to use your private service" name="textInput">
<div id="send-button">
<label for="send-button" class="submit-button">Send</label>
</div>
</div>
</div>
<div class="" style="margin-left:20px;max-width: fit-content;">
<div style="color: white;margin-bottom: 2rem;">
<h4>How it works</h4>
<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>
<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>
</div>
</div>
<h3 style="margin-left:10px">Activity Log</h3>
<p class="output-container">
<span id="output"></div>
</p>
<script src="index.ts"></script>
</body>
</html>
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/src/index.ts
================================================
// The address that is given to us from our mixnet client.
var ourAddress: string;
// Address we want to send our messages to. Replace it with the address of your Service Provider's Nym client!
var targetAddress: string = '6V5eEguz4rUsfntVLKQuD2ymgdY5iDKCV2GY2EH3CxG4.AKdk22atwRaVkN2PLEDsWUKKDc3ieNm1avKqVGgmJx8s@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE';
// Variable that holds our websocket connection data.
var websocketConnection: any;
async function main() {
var port = '1977' // Nym Websocket Client listens on 1977 by default.
var localClientUrl = "ws://127.0.0.1:" + port;
// Set up and handle websocket connection to our desktop client.
websocketConnection = await connectWebsocket(localClientUrl).then(function (c) {
return c;
}).catch(function (err) {
displayClientMessage("Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port " + port + "?");
})
websocketConnection.onmessage = function (e) {
handleResponse(e);
};
sendSelfAddressRequest();
// Set up the send button
const sendButton = document.querySelector('#send-button');
sendButton?.addEventListener('click', function handleClick(event) {
sendMessageToMixnet();
});
}
// 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
function sendSelfAddressRequest() {
var selfAddress = {
type: "selfAddress"
}
displayJsonSend(selfAddress);
websocketConnection.send(JSON.stringify(selfAddress));
}
// Function that gets the form data and sends that to the mixnet in a stringified JSON format.
function sendMessageToMixnet() {
// Access our form's elements current values
var nameInput = (<HTMLInputElement>document.getElementById("nameInput")).value;
var textInput = (<HTMLInputElement>document.getElementById("textInput")).value;
// construct the content of our message to send through the mixnet
const messageContentToSend = {
name : nameInput,
comment : textInput,
}
// construct our message object to send to the SP via the mixnet
const message = {
type: "sendAnonymous",
message: JSON.stringify(messageContentToSend),
recipient: targetAddress,
replySurbs: 5
}
// Display the json data you're sending to the SP on the UI
displayJsonSend(message);
// Send our message object via out via our websocket connection.
websocketConnection.send(JSON.stringify(message));
}
// Display responses into our activity log.
function displayJsonSend(message) {
let sendDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: #36d481')
let paragraphContent = document.createTextNode("sent >>> " + JSON.stringify(message))
paragraph.appendChild(paragraphContent)
sendDiv.appendChild(paragraph)
document.getElementById("output").appendChild(sendDiv)
}
// Connect to a websocket.
function connectWebsocket(url) {
return new Promise(function (resolve, reject) {
var server = new WebSocket(url);
console.log('connecting to Websocket Server (Nym Client)...')
server.onopen = function () {
resolve(server);
};
server.onerror = function (err) {
reject(err);
};
});
}
// Display messages that relate to initialising our client + client status in our activity log.
function displayClientMessage(message) {
document.getElementById("output").innerHTML += "<p>" + message + "</p >";
}
// Handle any messages that come back down the websocket.
function handleResponse(resp) {
try {
let response = JSON.parse(resp.data);
if (response.type == "error") {
displayJsonResponse("Server responded with error: " + response.message);
} else if (response.type == "selfAddress") {
ourAddress = response.address;
displayClientMessage("Our address is: " + ourAddress);
} else if (response.type == "received") {
handleReceivedTextMessage(response)
}
} catch (_) {
displayJsonResponse(resp.data)
}
}
// Handle any string message values that are received through messages sent back to us.
function handleReceivedTextMessage(message) {
const text = JSON.parse(message.message)
displayJsonResponse(text)
}
// Display websocket responses in the Activity Log.
function displayJsonResponse(message) {
let receivedDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: orange')
let textNode = document.createTextNode("received >>> " + message.text)
paragraph.appendChild(textNode)
receivedDiv.appendChild(paragraph)
document.getElementById("output").appendChild(receivedDiv)
}
main();
================================================
FILE: typescript/simple-service-provider-tutorial/user-client/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"lib": ["es2015"]
}
gitextract_datox1l_/
├── README.md
├── rust/
│ └── chain-query-service/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── bin/
│ │ ├── client.rs
│ │ └── service.rs
│ └── src/
│ ├── client.rs
│ ├── lib.rs
│ └── service.rs
└── typescript/
└── simple-service-provider-tutorial/
├── README.md
├── service-provider/
│ ├── .gitignore
│ ├── README.md
│ ├── nodemon.json
│ ├── package.json
│ ├── src/
│ │ └── index.ts
│ └── tsconfig.json
└── user-client/
├── .gitignore
├── README.md
├── assets/
│ └── styles.css
├── package.json
├── src/
│ ├── index.html
│ └── index.ts
└── tsconfig.json
SYMBOL INDEX (39 symbols across 7 files)
FILE: rust/chain-query-service/bin/client.rs
type Cli (line 10) | struct Cli {
type Commands (line 16) | enum Commands {
type QueryBalance (line 21) | struct QueryBalance {
function main (line 29) | async fn main() -> anyhow::Result<()> {
FILE: rust/chain-query-service/bin/service.rs
function main (line 11) | async fn main() -> anyhow::Result<()> {
FILE: rust/chain-query-service/src/client.rs
function query_balance (line 8) | pub async fn query_balance(
FILE: rust/chain-query-service/src/lib.rs
constant DEFAULT_VALIDATOR_RPC (line 13) | pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymt...
constant DEFAULT_DENOM (line 14) | pub const DEFAULT_DENOM: &str = "unym";
constant DEFAULT_PREFIX (line 15) | pub const DEFAULT_PREFIX: &str = "n";
type BalanceRequest (line 18) | pub struct BalanceRequest {
type BalanceResponse (line 24) | pub struct BalanceResponse {
type RequestTypes (line 29) | pub enum RequestTypes {
method serialize (line 34) | pub fn serialize(&self) -> Vec<u8> {
method try_deserialize (line 38) | pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {
type ResponseTypes (line 44) | pub enum ResponseTypes {
method serialize (line 49) | pub fn serialize(&self) -> Vec<u8> {
method try_deserialize (line 53) | pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {
function create_client (line 59) | pub async fn create_client(config_path: PathBuf) -> MixnetClient {
function wait_for_non_empty_message (line 71) | pub async fn wait_for_non_empty_message(
function handle_response (line 83) | pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result<...
function handle_request (line 87) | pub fn handle_request(
function balance_response_serialization (line 99) | fn balance_response_serialization() {
function parsing_balance_response (line 110) | fn parsing_balance_response() {
FILE: rust/chain-query-service/src/service.rs
function create_broadcaster (line 6) | pub async fn create_broadcaster() -> anyhow::Result<HttpClient> {
function get_balance (line 11) | pub async fn get_balance(
FILE: typescript/simple-service-provider-tutorial/service-provider/src/index.ts
function main (line 6) | async function main() {
function handleResponse (line 26) | function handleResponse(responseMessageEvent : MessageEvent) {
function sendMessageToMixnet (line 51) | function sendMessageToMixnet(senderTag: string) {
function sendSelfAddressRequest (line 70) | function sendSelfAddressRequest() {
function connectWebsocket (line 78) | function connectWebsocket(url : string) {
FILE: typescript/simple-service-provider-tutorial/user-client/src/index.ts
function main (line 10) | async function main() {
function sendSelfAddressRequest (line 36) | function sendSelfAddressRequest() {
function sendMessageToMixnet (line 45) | function sendMessageToMixnet() {
function displayJsonSend (line 73) | function displayJsonSend(message) {
function connectWebsocket (line 85) | function connectWebsocket(url) {
function displayClientMessage (line 99) | function displayClientMessage(message) {
function handleResponse (line 104) | function handleResponse(resp) {
function handleReceivedTextMessage (line 121) | function handleReceivedTextMessage(message) {
function displayJsonResponse (line 127) | function displayJsonResponse(message) {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
{
"path": "README.md",
"chars": 834,
"preview": "# Nym Developer Tutorials Code\nRepo storing the code used in the Nym developer tutorials, which can be found on the [Dev"
},
{
"path": "rust/chain-query-service/.gitignore",
"chars": 8,
"preview": "/target\n"
},
{
"path": "rust/chain-query-service/Cargo.toml",
"chars": 893,
"preview": "[package]\nname = \"chain_query\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc."
},
{
"path": "rust/chain-query-service/README.md",
"chars": 647,
"preview": "Query the balance of account on Sandbox testnet blockchain through the mixnet with the Rust SDK.\n\n[//]: # (_Laying the g"
},
{
"path": "rust/chain-query-service/bin/client.rs",
"chars": 1737,
"preview": "use clap::{Args, Parser, Subcommand};\nuse chain_query::{client::query_balance, create_client};\nuse nym_sdk::mixnet::Reci"
},
{
"path": "rust/chain-query-service/bin/service.rs",
"chars": 2113,
"preview": "use chain_query::{\n create_client, handle_request,\n service::{create_broadcaster, get_balance},\n BalanceRespons"
},
{
"path": "rust/chain-query-service/src/client.rs",
"chars": 1175,
"preview": "use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_VALIDATOR_RPC};\nuse cosmrs::AccountId;\nus"
},
{
"path": "rust/chain-query-service/src/lib.rs",
"chars": 3383,
"preview": "use anyhow::bail;\nuse cosmrs::AccountId;\nuse nym_sdk::mixnet::{\n AnonymousSenderTag, MixnetClient, MixnetClientBuilde"
},
{
"path": "rust/chain-query-service/src/service.rs",
"chars": 739,
"preview": "use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC};\nuse cosmrs::rpc::HttpClient;\nuse cosmrs::AccountId;\n"
},
{
"path": "typescript/simple-service-provider-tutorial/README.md",
"chars": 626,
"preview": "# Simple Service Provider \n\nThe code for the first Nym developer tutorial, building a simple Service Provider and some c"
},
{
"path": "typescript/simple-service-provider-tutorial/service-provider/.gitignore",
"chars": 42,
"preview": "*.cache\nnode_modules\n.DS_Store\n.DS_Store?\n"
},
{
"path": "typescript/simple-service-provider-tutorial/service-provider/README.md",
"chars": 454,
"preview": "Simple Service Provider using Nym Client Websocket - Service Provider Code\n\nSetup the project using 'npm install'\n\nRun t"
},
{
"path": "typescript/simple-service-provider-tutorial/service-provider/nodemon.json",
"chars": 116,
"preview": "{\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",
"chars": 450,
"preview": "{\n \"name\": \"service-provider\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"sta"
},
{
"path": "typescript/simple-service-provider-tutorial/service-provider/src/index.ts",
"chars": 3054,
"preview": "import WebSocket, { MessageEvent } from \"ws\";\n\nvar ourAddress: string;\nvar websocketConnection: any;\n\nasync fun"
},
{
"path": "typescript/simple-service-provider-tutorial/service-provider/tsconfig.json",
"chars": 420,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es2017\", \n \"lib\": [\n \"es6\"\n ],\n \"module\": \"Node16\", \n "
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/.gitignore",
"chars": 54,
"preview": "*.cache\nnode_modules\n.DS_Store\n.DS_Store?\n/dist\n.cache"
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/README.md",
"chars": 634,
"preview": "Simple Service Provider using Nym Client Websocket - User Client Code\n\nSetup the project using 'npm install'\n\nRun the ap"
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/assets/styles.css",
"chars": 2806,
"preview": ":host {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica,\n Arial, sans-serif, \"A"
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/package.json",
"chars": 326,
"preview": "{\n \"name\": \"user-client\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": "
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/src/index.html",
"chars": 1858,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>Mixnet Websocket Starter Client</title>\n"
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/src/index.ts",
"chars": 4979,
"preview": "// The address that is given to us from our mixnet client.\nvar ourAddress: string;\n\n// Address we want to send our messa"
},
{
"path": "typescript/simple-service-provider-tutorial/user-client/tsconfig.json",
"chars": 233,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"esModuleInterop\": true,\n \"target\": \"es6\",\n "
}
]
About this extraction
This page contains the full source code of the nymtech/developer-tutorials GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (26.9 KB), approximately 7.6k tokens, and a symbol index with 39 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.