nevmes-market intial commit

This commit is contained in:
creating2morrow 2023-05-29 06:14:01 -04:00
parent 69b399d25b
commit 21978de991
25 changed files with 3446 additions and 387 deletions

12
Cargo.lock generated
View file

@ -1934,6 +1934,7 @@ dependencies = [
"nevmes_contact",
"nevmes_core",
"nevmes_gui",
"nevmes_market",
"nevmes_message",
"rocket",
]
@ -2007,6 +2008,17 @@ dependencies = [
"wasm-bindgen-futures",
]
[[package]]
name = "nevmes_market"
version = "0.5.0-alpha"
dependencies = [
"chrono",
"env_logger",
"log 0.4.17",
"nevmes_core",
"rocket",
]
[[package]]
name = "nevmes_message"
version = "0.5.0-alpha"

View file

@ -11,5 +11,6 @@ nevmes_auth = { path = "./nevmes-auth" }
nevmes_contact = { path = "./nevmes-contact" }
nevmes_core = { path = "./nevmes-core" }
nevmes_gui = { path = "./nevmes-gui" }
nevmes_market = { path = "./nevmes-market" }
nevmes_message = { path = "./nevmes-message" }
rocket = { version = "0.5.0-rc.2", features = ["json"] }

View file

@ -89,6 +89,9 @@ pub struct Args {
/// Messaging sending port
#[arg(long, help = "Set app message sending port", default_value = "9045")]
pub message_port: u16,
/// Marketplace admin port
#[arg(long, help = "Set app marketplace admin port", default_value = "9046")]
pub marketplace_port: u16,
/// Auto trust contact gpg keys (DISABLED)
#[arg(
long,

View file

@ -175,7 +175,7 @@ impl Message {
}
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct User {
pub uid: String,
@ -217,13 +217,13 @@ impl User {
}
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Product {
pub pid: String,
pub vid: String,
pub in_stock: bool,
pub description: String,
pub image: Vec<u8>,
pub in_stock: bool,
pub name: String,
pub price: i64,
pub qty: i64,
@ -233,8 +233,8 @@ impl Default for Product {
fn default() -> Self {
Product {
pid: utils::empty_string(),
vid: utils::empty_string(),
description: utils::empty_string(),
image: Vec::new(),
in_stock: false,
name: utils::empty_string(),
price: 0,
@ -245,16 +245,17 @@ impl Default for Product {
impl Product {
pub fn to_db(p: &Product) -> String {
let image: String = hex::encode(&p.image);
format!(
"{}:{}:{}:{}:{}:{}",
p.vid, p.description, p.in_stock, p.name, p.price, p.qty
p.description, image, p.in_stock, p.name, p.price, p.qty
)
}
pub fn from_db(k: String, v: String) -> Product {
let values = v.split(":");
let mut v: Vec<String> = values.map(|s| String::from(s)).collect();
let vid = v.remove(0);
let description = v.remove(0);
let image = hex::decode(v.remove(0)).unwrap_or(Vec::new());
let in_stock = match v.remove(0).parse::<bool>() {
Ok(b) => b,
Err(_) => false,
@ -270,8 +271,8 @@ impl Product {
};
Product {
pid: k,
vid,
description,
image,
in_stock,
name,
price,
@ -281,6 +282,7 @@ impl Product {
pub fn update(
p: Product,
description: String,
image: Vec<u8>,
in_stock: bool,
name: String,
price: i64,
@ -288,8 +290,8 @@ impl Product {
) -> Product {
Product {
pid: p.pid,
vid: p.vid,
description,
image,
in_stock,
name,
price,
@ -298,74 +300,200 @@ impl Product {
}
}
// TODO: add mediator fields
#[derive(Debug)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Order {
pub orid: String,
pub c_id: String,
pub p_id: String,
pub v_id: String,
pub xmr_address: String,
pub cust_msig_info: String,
pub cust_msig_txset: String,
/// Order customer id is their .b32.i2p address
pub cid: String,
pub pid: String,
pub cust_kex_1: String,
pub cust_kex_2: String,
pub cust_kex_3: String,
pub cust_msig_make: String,
pub cust_msig_prepare: String,
pub cust_msig_txset: String,
pub date: i64,
pub deliver_date: i64,
pub ship_date: i64,
pub hash: String,
pub msig_prepare: String,
pub msig_make: String,
pub msig_kex_1: String,
pub msig_kex_2: String,
pub msig_kex_3: String,
pub mediator_kex_1: String,
pub mediator_kex_2: String,
pub mediator_kex_3: String,
pub mediator_msig_make: String,
pub mediator_msig_prepare: String,
pub ship_date: i64,
/// This is the final destination for the payment
pub subaddress: String,
pub status: String,
pub quantity: i64,
pub vend_kex_1: String,
pub vend_kex_2: String,
pub vend_kex_3: String,
pub vend_msig_info: String,
pub vend_msig_make: String,
pub vend_msig_prepare: String,
pub vend_msig_txset: String,
pub xmr_address: String,
}
impl Default for Order {
fn default() -> Self {
Order {
orid: utils::empty_string(),
c_id: utils::empty_string(),
p_id: utils::empty_string(),
v_id: utils::empty_string(),
cid: utils::empty_string(),
pid: utils::empty_string(),
xmr_address: utils::empty_string(),
cust_msig_info: utils::empty_string(),
cust_msig_txset: utils::empty_string(),
cust_kex_1: utils::empty_string(),
cust_kex_2: utils::empty_string(),
cust_kex_3: utils::empty_string(),
cust_msig_make: utils::empty_string(),
cust_msig_prepare: utils::empty_string(),
cust_msig_txset: utils::empty_string(),
date: 0,
deliver_date: 0,
ship_date: 0,
hash: utils::empty_string(),
msig_prepare: utils::empty_string(),
msig_make: utils::empty_string(),
msig_kex_1: utils::empty_string(),
msig_kex_2: utils::empty_string(),
msig_kex_3: utils::empty_string(),
mediator_kex_1: utils::empty_string(),
mediator_kex_2: utils::empty_string(),
mediator_kex_3: utils::empty_string(),
mediator_msig_make: utils::empty_string(),
mediator_msig_prepare: utils::empty_string(),
subaddress: utils::empty_string(),
status: utils::empty_string(),
quantity: 0,
vend_kex_1: utils::empty_string(),
vend_kex_2: utils::empty_string(),
vend_kex_3: utils::empty_string(),
vend_msig_info: utils::empty_string(),
vend_msig_make: utils::empty_string(),
vend_msig_prepare: utils::empty_string(),
vend_msig_txset: utils::empty_string(),
}
}
}
#[derive(Debug, Deserialize)]
impl Order {
pub fn to_db(o: &Order) -> String {
format!(
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
o.cid, o.pid, o.cust_kex_1, o.cust_kex_2, o.cust_kex_3, o.cust_msig_make,
o.cust_msig_prepare, o.cust_msig_txset, o.date, o.deliver_date,
o.hash, o.mediator_msig_make, o.mediator_msig_prepare, o.mediator_kex_1,
o.mediator_kex_2, o.mediator_kex_3, o.ship_date, o.subaddress, o.status,
o.quantity, o.vend_kex_1, o.vend_kex_2, o.vend_kex_3, o.vend_msig_make,
o.vend_msig_prepare, o.vend_msig_txset, o.xmr_address,
)
}
pub fn from_db(k: String, v: String) -> Order {
let values = v.split(":");
let mut v: Vec<String> = values.map(|s| String::from(s)).collect();
let orid = k;
let cid = v.remove(0);
let pid = v.remove(0);
let cust_kex_1 = v.remove(0);
let cust_kex_2 = v.remove(0);
let cust_kex_3 = v.remove(0);
let cust_msig_make = v.remove(0);
let cust_msig_prepare = v.remove(0);
let cust_msig_txset = v.remove(0);
let date = match v.remove(0).parse::<i64>() {
Ok(d) => d,
Err(_) => 0,
};
let deliver_date = match v.remove(0).parse::<i64>() {
Ok(d) => d,
Err(_) => 0,
};
let hash = v.remove(0);
let mediator_msig_make = v.remove(0);
let mediator_msig_prepare = v.remove(0);
let mediator_kex_1 = v.remove(0);
let mediator_kex_2 = v.remove(0);
let mediator_kex_3 = v.remove(0);
let ship_date = match v.remove(0).parse::<i64>() {
Ok(d) => d,
Err(_) => 0,
};
let subaddress = v.remove(0);
let status = v.remove(0);
let quantity = match v.remove(0).parse::<i64>() {
Ok(d) => d,
Err(_) => 0,
};
let vend_kex_1 = v.remove(0);
let vend_kex_2 = v.remove(0);
let vend_kex_3 = v.remove(0);
let vend_msig_make = v.remove(0);
let vend_msig_prepare = v.remove(0);
let vend_msig_txset = v.remove(0);
let xmr_address = v.remove(0);
Order {
orid,
cid,
pid,
cust_kex_1,
cust_kex_2,
cust_kex_3,
cust_msig_make,
cust_msig_prepare,
cust_msig_txset,
date,
deliver_date,
hash,
mediator_kex_1,
mediator_kex_2,
mediator_kex_3,
mediator_msig_make,
mediator_msig_prepare,
ship_date,
subaddress,
status,
quantity,
vend_kex_1,
vend_kex_2,
vend_kex_3,
vend_msig_make,
vend_msig_prepare,
vend_msig_txset,
xmr_address,
}
}
pub fn update(
orid: String,
o: Order,
) -> Order {
Order {
orid,
cid: String::from(&o.cid),
pid: String::from(&o.pid),
cust_kex_1: String::from(&o.cust_kex_1),
cust_kex_2: String::from(&o.cust_kex_2),
cust_kex_3: String::from(&o.cust_kex_3),
cust_msig_make: String::from(&o.cust_msig_make),
cust_msig_prepare: String::from(&o.cust_msig_make),
cust_msig_txset: String::from(&o.cust_msig_txset),
date: o.date,
deliver_date: o.deliver_date,
hash: String::from(&o.hash),
mediator_kex_1: String::from(&o.mediator_kex_1),
mediator_kex_2: String::from(&o.mediator_kex_2),
mediator_kex_3: String::from(&o.mediator_kex_3),
mediator_msig_make: String::from(&o.mediator_msig_make),
mediator_msig_prepare: String::from(&o.mediator_msig_prepare),
ship_date: o.ship_date,
subaddress: String::from(&o.subaddress),
status: String::from(&o.status),
quantity: o.quantity,
vend_kex_1: String::from(&o.vend_kex_1),
vend_kex_2: String::from(&o.vend_kex_2),
vend_kex_3: String::from(&o.vend_kex_3),
vend_msig_make: String::from(&o.vend_msig_make),
vend_msig_prepare: String::from(&o.vend_msig_prepare),
vend_msig_txset: String::from(&o.vend_msig_txset),
xmr_address: String::from(&o.xmr_address),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Dispute {
pub did: String,

View file

@ -598,39 +598,6 @@ pub async fn make_wallet(info: Vec<String>) -> reqres::XmrRpcMakeResponse {
}
}
/// Performs the xmr rpc 'finalize_multisig' method
pub async fn finalize_wallet(info: Vec<String>) -> reqres::XmrRpcFinalizeResponse {
info!("executing {}", RpcFields::Finalize.value());
let client = reqwest::Client::new();
let host = get_rpc_host();
let params = reqres::XmrRpcFinalizeParams {
multisig_info: info,
};
let req = reqres::XmrRpcFinalizeRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(),
method: RpcFields::Finalize.value(),
params,
};
let login: RpcLogin = get_rpc_creds();
match client
.post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => {
let res = response.json::<reqres::XmrRpcFinalizeResponse>().await;
debug!("{} response: {:?}", RpcFields::Finalize.value(), res);
match res {
Ok(res) => res,
_ => Default::default(),
}
}
Err(_) => Default::default(),
}
}
/// Performs the xmr rpc 'export_multisig_info' method
pub async fn export_multisig_info() -> reqres::XmrRpcExportResponse {
info!("executing {}", RpcFields::Export.value());
@ -725,7 +692,7 @@ pub async fn sign_multisig(tx_data_hex: String) -> reqres::XmrRpcSignMultisigRes
/// Performs the xmr rpc 'exchange_multisig_keys' method
pub async fn exchange_multisig_keys(
force_update_use_with_caution: bool,
multisig_info: String,
multisig_info: Vec<String>,
password: String,
) -> reqres::XmrRpcExchangeMultisigKeysResponse {
info!("executing: {}", RpcFields::ExchangeMultisigKeys.value());

View file

@ -1,4 +1,4 @@
use crate::{utils, models};
use crate::utils;
use serde::{
Deserialize,
Serialize,
@ -62,7 +62,7 @@ pub struct XmrRpcSignMultisigParams {
#[derive(Deserialize, Serialize, Debug)]
pub struct XmrRpcExchangeMultisigKeysParams {
pub force_update_use_with_caution: bool,
pub multisig_info: String,
pub multisig_info: Vec<String>,
pub password: String,
}
@ -1080,307 +1080,3 @@ impl Default for ErrorResponse {
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetCustomerResponse {
pub address: String,
pub cid: String,
pub name: String,
pub pgp: String,
}
impl Default for GetCustomerResponse {
fn default() -> Self {
GetCustomerResponse {
address: utils::empty_string(),
cid: utils::empty_string(),
name: utils::empty_string(),
pgp: utils::empty_string(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetVendorResponse {
pub vid: String,
pub active: bool,
pub address: String,
pub description: String,
pub name: String,
pub pgp: String,
}
impl Default for GetVendorResponse {
fn default() -> Self {
GetVendorResponse {
vid: utils::empty_string(),
active: false,
address: utils::empty_string(),
description: utils::empty_string(),
name: utils::empty_string(),
pgp: utils::empty_string(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetProductResponse {
pub pid: String,
pub in_stock: bool,
pub description: String,
pub name: String,
pub price: i64,
pub qty: i64,
}
impl Default for GetProductResponse {
fn default() -> Self {
GetProductResponse {
pid: utils::empty_string(),
in_stock: false,
description: utils::empty_string(),
name: utils::empty_string(),
price: 0,
qty: 0,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetVendorProductsResponse {
pub products: Vec<GetProductResponse>,
}
impl Default for GetVendorProductsResponse {
fn default() -> Self {
GetVendorProductsResponse {
products: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetAuthResponse {
pub address: String,
pub aid: String,
pub created: i64,
pub cvid: String,
pub data: String,
pub token: String,
}
impl Default for GetAuthResponse {
fn default() -> Self {
GetAuthResponse {
address: utils::empty_string(),
aid: utils::empty_string(),
created: 0,
cvid: utils::empty_string(),
data: utils::empty_string(),
token: utils::empty_string(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetDisputeResponse {
pub orid: String,
pub created: i64,
pub tx_set: String,
}
impl Default for GetDisputeResponse {
fn default() -> Self {
GetDisputeResponse {
orid: utils::empty_string(),
created: 0,
tx_set: utils::empty_string(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetOrderResponse {
pub orid: String,
pub pid: String,
pub xmr_address: String,
pub cust_msig_info: String,
pub cust_kex_1: String,
pub cust_kex_2: String,
pub cust_kex_3: String,
pub date: i64,
pub deliver_date: i64,
pub ship_date: i64,
pub hash: String,
pub msig_prepare: String,
pub msig_make: String,
pub msig_kex_1: String,
pub msig_kex_2: String,
pub msig_kex_3: String,
pub subaddress: String,
pub status: String,
pub quantity: i64,
pub vend_kex_1: String,
pub vend_kex_2: String,
pub vend_kex_3: String,
pub vend_msig_info: String,
}
impl Default for GetOrderResponse {
fn default() -> Self {
GetOrderResponse {
orid: utils::empty_string(),
pid: utils::empty_string(),
xmr_address: utils::empty_string(),
cust_msig_info: utils::empty_string(),
cust_kex_1: utils::empty_string(),
cust_kex_2: utils::empty_string(),
cust_kex_3: utils::empty_string(),
date: 0,
deliver_date: 0,
ship_date: 0,
hash: utils::empty_string(),
msig_prepare: utils::empty_string(),
msig_make: utils::empty_string(),
msig_kex_1: utils::empty_string(),
msig_kex_2: utils::empty_string(),
msig_kex_3: utils::empty_string(),
subaddress: utils::empty_string(),
status: utils::empty_string(),
quantity: 0,
vend_kex_1: utils::empty_string(),
vend_kex_2: utils::empty_string(),
vend_kex_3: utils::empty_string(),
vend_msig_info: utils::empty_string(),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct GetOrdersResponse {
pub orders: Vec<GetOrderResponse>,
}
impl Default for GetOrdersResponse {
fn default() -> Self {
GetOrdersResponse {
orders: Vec::new(),
}
}
}
// START response builders
impl GetDisputeResponse {
pub fn build(m_dispute: models::Dispute) -> Self {
GetDisputeResponse {
orid: m_dispute.orid,
created: m_dispute.created,
tx_set: m_dispute.tx_set,
}
}
}
impl GetProductResponse {
pub fn build(m_product: models::Product) -> Self {
GetProductResponse {
pid: m_product.pid,
in_stock: m_product.in_stock,
description: m_product.description,
name: m_product.name,
price: m_product.price,
qty: m_product.qty,
}
}
}
impl GetVendorProductsResponse {
pub fn build(m_products: Vec<models::Product>) -> Self {
let mut v_res: Vec<GetProductResponse> = Vec::new();
for m in m_products {
let p_res: GetProductResponse = GetProductResponse {
pid: m.pid,
in_stock: m.in_stock,
description: m.description,
name: m.name,
price: m.price,
qty: m.qty,
};
v_res.push(p_res);
}
GetVendorProductsResponse { products: v_res }
}
}
impl GetOrderResponse {
pub fn build(pid: String, m_order: models::Order) -> Self {
GetOrderResponse {
orid: m_order.orid,
pid,
xmr_address: m_order.xmr_address,
cust_msig_info: m_order.cust_msig_info,
cust_kex_1: m_order.cust_kex_1,
cust_kex_2: m_order.cust_kex_2,
cust_kex_3: m_order.cust_kex_3,
date: m_order.date,
deliver_date: m_order.deliver_date,
ship_date: m_order.ship_date,
hash: m_order.hash,
msig_prepare: m_order.msig_prepare,
msig_make: m_order.msig_make,
msig_kex_1: m_order.msig_kex_1,
msig_kex_2: m_order.msig_kex_2,
msig_kex_3: m_order.msig_kex_3,
subaddress: m_order.subaddress,
status: m_order.status,
quantity: m_order.quantity,
vend_kex_1: m_order.vend_kex_1,
vend_kex_2: m_order.vend_kex_2,
vend_kex_3: m_order.vend_kex_3,
vend_msig_info: m_order.vend_msig_info,
}
}
}
impl GetOrdersResponse {
pub fn build(m_orders: Vec<models::Order>) -> Self {
let mut v_res: Vec<GetOrderResponse> = Vec::new();
for m in m_orders {
let o_res: GetOrderResponse = GetOrderResponse {
orid: m.orid,
pid: m.p_id,
xmr_address: m.xmr_address,
cust_msig_info: m.cust_msig_info,
cust_kex_1: m.cust_kex_1,
cust_kex_2: m.cust_kex_2,
cust_kex_3: m.cust_kex_3,
date: m.date,
deliver_date: m.deliver_date,
ship_date: m.ship_date,
hash: m.hash,
msig_prepare: m.msig_prepare,
msig_make: m.msig_make,
msig_kex_1: m.msig_kex_1,
msig_kex_2: m.msig_kex_2,
msig_kex_3: m.msig_kex_3,
subaddress: m.subaddress,
status: m.status,
quantity: m.quantity,
vend_kex_1: m.vend_kex_1,
vend_kex_2: m.vend_kex_2,
vend_kex_3: m.vend_kex_3,
vend_msig_info: m.vend_msig_info,
};
v_res.push(o_res);
}
GetOrdersResponse { orders: v_res }
}
}
// END response builders

View file

@ -179,6 +179,12 @@ pub fn get_app_contact_port() -> u16 {
args.contact_port
}
/// app message port
pub fn get_app_market_port() -> u16 {
let args = args::Args::parse();
args.marketplace_port
}
/// app message port
pub fn get_app_message_port() -> u16 {
let args = args::Args::parse();
@ -355,6 +361,15 @@ fn start_micro_servers() {
.spawn()
.expect("failed to start contact server");
debug!("{:?}", c_output.stdout);
info!("starting marketplace admin server");
let mut market_path = "nevmes-contact/target/debug/nevmes_market";
if env == ReleaseEnvironment::Production {
market_path = "nevmes_market";
}
let market_output = std::process::Command::new(market_path)
.spawn()
.expect("failed to start marketplace server");
debug!("{:?}", market_output.stdout);
info!("starting message server");
let mut message_path = "nevmes-message/target/debug/nevmes_message";
if env == ReleaseEnvironment::Production {

7
nevmes-market/.gitignore vendored Executable file
View file

@ -0,0 +1,7 @@
/target
/core
/test-lmdb
/wallet
monero-wallet-rpc.log
notes.txt
.env

2660
nevmes-market/Cargo.lock generated Executable file

File diff suppressed because it is too large Load diff

12
nevmes-market/Cargo.toml Executable file
View file

@ -0,0 +1,12 @@
[package]
name = "nevmes_market"
version = "0.5.0-alpha"
edition = "2021"
publish = false
[dependencies]
chrono = "0.4.23"
env_logger = "0.10.0"
nevmes_core = { path = "../nevmes-core" }
log = "0.4"
rocket = { version = "0.5.0-rc.2", features = ["json"] }

21
nevmes-market/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 creating2morrow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

64
nevmes-market/README.md Executable file
View file

@ -0,0 +1,64 @@
# nevmes-market
## High-Level Order Lifecycle
* vendor adds a new product with description and price
* customer orders product
* vendor creates order, multisig wallet and prepares it
| | prepare | make | exchange |
|-- |-- |-- |-- |
|vend | X | | |
|cust | | | |
|med | | | |
* customer saves prepare info from vendor, creates multisig wallet
| | prepare | make | exchange |
|-- |-- |-- |-- |
|vend | X | | |
|cust | X | | |
|med | | | |
* customer sends both prepare infos to mediator
* mediator creates multisig wallet, prepares and makes it
| | prepare | make | exchange |
|-- |-- |-- |-- |
|vend | X | | |
|cust | X | | |
|med | X | X | |
* customer makes multisig wallet and sends both outputs to vendor
| | prepare | make | exchange |
|-- |-- |-- |-- |
|vend | X | | |
|cust | X | X | |
|med | X | X | |
* vendor makes and calls to exchange multisig keys
| | prepare | make | exchange |
|-- |-- |-- |-- |
|vend | X | X | X |
|cust | X | X | |
|med | X | X | |
* customer sends output to mediator who then exchanges multisig keys
| | prepare | make | exchange |
|-- |-- |-- |-- |
|vend | X | X | X |
|cust | X | X | X |
|med | X | X | X |
* customer funds wallet and exports to vendor and mediator
* vendor and mediator import multisig info
* customer signs multisig txset and sends to mediator
* mediator requests tracking number from vendor
* mediator relase signed txset to vendor
* vendor signs and submits signed txset
* in case of dispute the mediator can sign multisig txset for customer refund
Reference: https://resilience365.com/monero-multisig-how-to/

75
nevmes-market/curl.md Normal file
View file

@ -0,0 +1,75 @@
# CURL / API docs
```bash
# [GET] get the current monero-wallet-rpc version
curl http://127.0.0.1:8000/xmr/version
# [POST] sign data (monero-wallet-rpc)
curl http://127.0.0.1:38083/json_rpc --digest -u user:pass -d '{"jsonrpc":"2.0","id":"0","method":"sign","params":{"data":"some data here"}}' -H 'Content-Type: application/json'
# [POST] get addresses (monero-wallet-rpc)
curl http://127.0.0.1:38083/json_rpc --digest -u user:pass -d '{"jsonrpc":"2.0","id":"0","method":"get_address","params":{"account_index":0,"address_index":[0]}}' -H 'Content-Type: application/json'
# [GET] login
# customer or vendor
# xmr address
# aid - auth id
# cvid - customer or vendor id (2nd api call finalizes login and creates it)
# data - random bytes to sign
# signature - generate signature with wallet private keys
curl http://127.0.0.1:8000/login/<customer|vendor>/<XMR_ADDRESS>/<SIGNATURE>/<AID>/CID
# [GET] information
# customer or vendor
# xmr address
# customer or vendor id
curl http://127.0.0.1:8000/<customer|vendor>/<XMR_ADDRESS>/<ID> -H 'token: <JWT>'
# [PATCH] update
# customer or vendor URI
# <id> - i32
# <data> - String
# <update_type> - Enum => 0 - active, 1 - description, 2 - name, 3 - pgp
curl -iv -X PATCH http://127.0.0.1:8000/<customer|vendor>/<XMR_ADDRESS>/update -d '{"cid": "CID", "name": "<name>", "pgp": "<pgp>", "xmr_address": "" }'
# [GET]
# create a new product
curl -iv http://127.0.0.1:8000/product/<XMR_ADDRESS>/create -H 'token: <JWT>'
# [GET]
# return all products for a vendor
curl -iv http://127.0.0.1:8000/products/<XMR_ADDRESS> -H 'token: <JWT>'
# [PATCH] update product
# <pid> - i32
# <data> - String
# <update_type> - Enum => 0 - in_stock, 1 - description, 2 - name, 3 - price 4 - qty
curl -X PATCH http://127.0.0.1:8000/product/<XMR_ADDRESS>/update/<pid>/<data>/<update_type> -H 'token: <JWT>'
# [GET]
# intialize an order for a customer
curl -iv http://127.0.0.1:8000/order/<XMR_ADDRESS>/create/<pid> -H 'token: <JWT>'
# [GET]
# get all orders
# xmr address
# customer | vendor
curl -iv http://127.0.0.1:8000/orders/<XMR_ADDRESS>/<customer | vendor> -H 'token: <JWT>'
# [PATCH]
# modify order
# UpdateType::CustomerKex1 => 0, // make output from customer
# UpdateType::CustomerKex2 => 1, // use this for funding kex
# UpdateType::CustomerKex3 => 2, // might need this later?
# UpdateType::CustomerMultisigInfo => 3, // prepare output from customer
# UpdateType::Deliver => 4, // customer has received the item, released txset
# UpdateType::Hash => 5, // tx hash from funding the wallet order
# UpdateType::Ship => 6, // update ship date, app doesn't store tracking numbers
# UpdateType::Subaddress => 7, // update address for payout
# UpdateType::VendorKex1 => 8, // make output from vendor
# UpdateType::VendorKex2 => 9, // use this for funding kex
# UpdateType::VendorKex3 => 10, // might need this later?
# UpdateType::VendorMultisigInfo => 11, // prepare output from vendor
# UpdateType::Quantity => 12, // this can be updated until wallet is funded
curl -X PATCH http://127.0.0.1:8000/order/<XMR_ADDRESS>/update/<pid>/<oid>/<data>/<update_type> -H 'token: <JWT>'
```

View file

@ -0,0 +1,110 @@
use rocket::http::Status;
use rocket::response::status::Custom;
use rocket::serde::json::Json;
use rocket::{get, patch, post};
use nevmes_core::*;
use crate::{dispute, product};
// JSON APIs
/// Create a product by passing vendor vid
#[post("/create", data = "<req_product>")]
pub async fn create_product(
req_product: Json<models::Product>,
_token: auth::BearerToken,
) -> Custom<Json<models::Product>> {
let m_product: models::Product = product::create(req_product);
Custom(
Status::Ok,
Json(m_product),
)
}
/// Update product information
#[patch("/<_address>/update", data="<product>")]
pub async fn update_product(
_address: String,
product: Json<models::Product>,
_token: auth::BearerToken,
) -> Custom<Json<models::Product>> {
let m_product: models::Product = product::modify(product);
Custom(
Status::Ok,
Json(m_product),
)
}
// /// Initialize order
// #[get("/<address>/create/<pid>")]
// pub async fn initialize_order(
// address: String,
// _token: auth::BearerToken,
// pid: String,
// ) -> Custom<Json<reqres::GetOrderResponse>> {
// // get the cid from the address after verification
// let m_customer = customer::find(address).await;
// let temp_pid = String::from(&pid);
// let m_order: models::Order = order::create(m_customer.cid, temp_pid).await;
// Custom(
// Status::Ok,
// Json(reqres::GetOrderResponse::build(pid, m_order)),
// )
// }
// /// Update order information from vendor
// #[patch("/update/<pid>/<oid>/<data>/vendor")]
// pub async fn update_order(
// _address: String,
// oid: String,
// pid: String,
// _token: auth::BearerToken,
// data: String,
// ) -> Custom<Json<reqres::GetOrderResponse>> {
// let temp_pid: String = String::from(&pid);
// let m_order: models::Order = order::modify(oid, pid, data, update_type).await;
// Custom(
// Status::Ok,
// Json(reqres::GetOrderResponse::build(temp_pid, m_order)),
// )
// }
// /// Get all orders
// /// by passing auth
// #[get("/<address>/<corv>")]
// pub async fn get_orders(
// address: String,
// corv: String,
// _token: auth::BearerToken,
// ) -> Custom<Json<reqres::GetOrdersResponse>> {
// let m_orders: Vec<models::Order> = order::find_all(address, corv).await;
// Custom(Status::Ok, Json(reqres::GetOrdersResponse::build(m_orders)))
// }
/// Create a dispute
#[post("/create", data = "<dispute>")]
pub async fn create_dispute(
dispute: Json<models::Dispute>,
_token: auth::BearerToken,
) -> Custom<Json<models::Dispute>> {
let m_dispute: models::Dispute = dispute::create(dispute);
Custom(
Status::Ok,
Json(m_dispute),
)
}
/// Create a dispute
#[get("/<did>")]
pub async fn get_dispute(
_token: auth::BearerToken,
did: String,
) -> Custom<Json<models::Dispute>> {
let m_dispute: models::Dispute = dispute::find(&did);
Custom(
Status::Ok,
Json(m_dispute),
)
}
// END JSON APIs

View file

@ -0,0 +1,31 @@
use nevmes_core::{db, models::*, utils};
use rocket::serde::json::Json;
use log::{debug, error, info};
/// Create a new dispute
pub fn create(d: Json<Dispute>) -> Dispute {
let f_did: String = format!("dispute{}", utils::generate_rnd());
info!("create dispute: {}", &f_did);
let new_dispute = Dispute {
did: String::from(&f_did),
created: chrono::offset::Utc::now().timestamp(),
orid: String::from(&d.orid),
tx_set: String::from(&d.tx_set),
};
debug!("insert dispute: {:?}", &d);
let s = db::Interface::open();
let k = &d.did;
db::Interface::write(&s.env, &s.handle, k, &Dispute::to_db(&new_dispute));
new_dispute
}
/// Dispute lookup
pub fn find(did: &String) -> Dispute {
let s = db::Interface::open();
let r = db::Interface::read(&s.env, &s.handle, &String::from(did));
if r == utils::empty_string() {
error!("dispute not found");
return Default::default()
}
Dispute::from_db(String::from(did), r)
}

6
nevmes-market/src/lib.rs Executable file
View file

@ -0,0 +1,6 @@
pub mod controller; // HTTP entry point
pub mod dispute; // Dispute repo/service layer
pub mod order; // Order repo/service layer
pub mod product; // Product repo/service layer
// DO NOT EDIT BELOW THIS LINE

22
nevmes-market/src/main.rs Executable file
View file

@ -0,0 +1,22 @@
#[macro_use]
extern crate rocket;
use nevmes_core::*;
use nevmes_market::*;
// The only changes in here should be mounting new controller methods
#[launch]
async fn rocket() -> _ {
let config = rocket::Config {
port: utils::get_app_market_port(),
..rocket::Config::debug_default()
};
env_logger::init();
log::info!("nevmes-auth is online");
rocket::custom(&config)
.mount("/dispute", routes![controller::create_dispute, controller::get_dispute])
// .mount("/order", routes![controller::initialize_order, controller::update_order])
// .mount("/orders", routes![controller::get_orders])
.mount("/product", routes![controller::create_product, controller::update_product])
}

111
nevmes-market/src/order.rs Normal file
View file

@ -0,0 +1,111 @@
// use nevmes_core::*;
// use log::{debug, error, info};
// use rocket::serde::json::Json;
// use crate::product;
// enum StatusType {
// Delivered,
// MultisigMissing,
// MulitsigComplete,
// Shipped,
// }
// impl StatusType {
// pub fn value(&self) -> String {
// match *self {
// StatusType::Delivered => String::from("Delivered"),
// StatusType::MultisigMissing => String::from("MultisigMissing"),
// StatusType::MulitsigComplete => String::from("MulitsigComplete"),
// StatusType::Shipped => String::from("Shipped"),
// }
// }
// }
// /// Create a skeleton for order
// pub fn create(cid: String, pid: String) -> models::Order {
// let ts = chrono::offset::Utc::now().timestamp();
// let orid: String = format!("O{}", utils::generate_rnd());
// let m_product: models::Product = product::find(&pid);
// let new_order = models::Order {
// orid,
// cid: String::from(&cid),
// pid: String::from(&pid),
// cust_kex_1: utils::empty_string(),
// cust_kex_2: utils::empty_string(),
// cust_kex_3: utils::empty_string(),
// cust_msig_make: utils::empty_string(),
// cust_msig_prepare: utils::empty_string(),
// cust_msig_txset: utils::empty_string(),
// date: 0,
// deliver_date: 0,
// hash: utils::empty_string(),
// mediator_kex_1: utils::empty_string(),
// mediator_kex_2: utils::empty_string(),
// mediator_kex_3: utils::empty_string(),
// mediator_msig_make: utils::empty_string(),
// mediator_msig_prepare: utils::empty_string(),
// ship_date: 0,
// subaddress: utils::empty_string(),
// status: utils::empty_string(),
// quantity: 0,
// vend_kex_1: utils::empty_string(),
// vend_kex_2: utils::empty_string(),
// vend_kex_3: utils::empty_string(),
// vend_msig_make: utils::empty_string(),
// vend_msig_prepare: utils::empty_string(),
// vend_msig_txset: utils::empty_string(),
// xmr_address: utils::empty_string(),
// };
// debug!("insert order: {:?}", new_order);
// let m_wallet = monero::create_wallet(String::from(&orid), &utils::empty_string()).await;
// if !m_wallet {
// error!("error creating msig wallet for order {}", &orid);
// }
// debug!("insert order: {:?}", &new_order);
// let s = db::Interface::open();
// let k = &new_order.orid;
// db::Interface::write(&s.env, &s.handle, k, &models::Order::to_db(&new_order));
// // in order to retrieve all orders, write keys to with ol
// let list_key = format!("ol");
// let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key));
// if r == utils::empty_string() {
// debug!("creating order index");
// }
// let order_list = [r, String::from(&orid)].join(",");
// debug!("writing order index {} for id: {}", order_list, list_key);
// db::Interface::write(&s.env, &s.handle, &String::from(list_key), &order_list);
// new_order
// }
// /// Lookup order
// pub fn find(oid: String) -> models::Order {
// let s = db::Interface::open();
// let r = db::Interface::read(&s.env, &s.handle, &String::from(&oid));
// if r == utils::empty_string() {
// error!("order not found");
// return Default::default();
// }
// models::Order::from_db(String::from(&oid), r)
// }
// /// Lookup all orders for customer
// pub async fn find_all_customer_orders(cid: String) -> Vec<models::Order> {
// let i_s = db::Interface::open();
// let i_list_key = format!("ol");
// let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key));
// if i_r == utils::empty_string() {
// error!("order index not found");
// }
// let i_v_oid = i_r.split(",");
// let i_v: Vec<String> = i_v_oid.map(|s| String::from(s)).collect();
// let mut orders: Vec<models::Order> = Vec::new();
// for o in i_v {
// let order: models::Order = find(o);
// if order.orid != utils::empty_string() && order.cid == cid {
// orders.push(order);
// }
// }
// orders
// }

View file

@ -0,0 +1,86 @@
// Product repo/service layer
use nevmes_core::{db, models::*, utils};
use log::{debug, error, info};
use rocket::serde::json::Json;
/// Create a new product
pub fn create(d: Json<Product>) -> Product {
let pid: String = format!("product{}", utils::generate_rnd());
let new_product = Product {
pid: String::from(&pid),
description: String::from(&d.description),
image: d.image.iter().cloned().collect(),
in_stock: d.in_stock,
name: String::from(&d.name),
price: d.price,
qty: d.qty,
};
debug!("insert product: {:?}", &new_product);
let s = db::Interface::open();
let k = &new_product.pid;
db::Interface::write(&s.env, &s.handle, k, &Product::to_db(&new_product));
// in order to retrieve all products, write keys to with pl
let list_key = format!("pl");
let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key));
if r == utils::empty_string() {
debug!("creating product index");
}
let product_list = [r, String::from(&pid)].join(",");
debug!("writing product index {} for id: {}", product_list, list_key);
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &product_list);
new_product
}
/// Single Product lookup
pub fn find(pid: &String) -> Product {
let s = db::Interface::open();
let r = db::Interface::read(&s.env, &s.handle, &String::from(pid));
if r == utils::empty_string() {
error!("product not found");
return Default::default();
}
Product::from_db(String::from(pid), r)
}
/// Product lookup for all
pub fn find_all() -> Vec<Product> {
let i_s = db::Interface::open();
let i_list_key = format!("pl");
let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key));
if i_r == utils::empty_string() {
error!("product index not found");
}
let i_v_pid = i_r.split(",");
let i_v: Vec<String> = i_v_pid.map(|s| String::from(s)).collect();
let mut products: Vec<Product> = Vec::new();
for p in i_v {
let product: Product = find(&p);
if product.pid != utils::empty_string() {
products.push(product);
}
}
products
}
/// Modify product
pub fn modify(p: Json<Product>) -> Product {
info!("modify product: {}", &p.pid);
let f_prod: Product = find(&p.pid);
if f_prod.pid == utils::empty_string() {
error!("product not found");
return Default::default();
}
let u_prod = Product::update(
f_prod,
String::from(&p.description),
p.image.iter().cloned().collect(),
p.in_stock,
String::from(&p.description),
p.price,
p.qty,
);
let s = db::Interface::open();
db::Interface::delete(&s.env, &s.handle, &u_prod.pid);
db::Interface::write(&s.env, &s.handle, &u_prod.pid, &Product::to_db(&u_prod));
return u_prod;
}

View file

@ -3,5 +3,6 @@ cd nevmes-auth && cargo build
cd ../nevmes-contact && cargo build
cd ../nevmes-core && cargo build
cd ../nevmes-gui && cargo build
cd ../nevmes-market && cargo build
cd ../nevmes-message && cargo build
cd ../ && RUST_LOG=debug cargo run $1

View file

@ -19,6 +19,8 @@ cd nevmes-auth && cargo build --release && cp target/release/nevmes_auth ../$LIN
cd ../
cd nevmes-contact && cargo build --release && cp target/release/nevmes_contact ../$LINUX_OUTPUT_DIR
cd ../
cd nevmes-market && cargo build --release && cp target/release/nevmes_market ../$LINUX_OUTPUT_DIR
cd ../
cd nevmes-message && cargo build --release && cp target/release/nevmes_message ../$LINUX_OUTPUT_DIR
cd ../
# make the bzip for linux

View file

@ -4,6 +4,7 @@ cd nevmes-auth && cargo clean
cd ../nevmes-contact && cargo clean
cd ../nevmes-core && cargo clean
cd ../nevmes-gui && cargo clean
cd ../nevmes-market && cargo clean
cd ../nevmes-message && cargo clean
cd ../ && cargo clean
rm -rf .build/

View file

@ -4,5 +4,6 @@ cd nevmes-auth && cargo +nightly fmt
cd ../nevmes-contact && cargo +nightly fmt
cd ../nevmes-core && cargo +nightly fmt
cd ../nevmes-gui && cargo +nightly fmt
cd ../nevmes-market && cargo +nightly fmt
cd ../nevmes-message && cargo +nightly fmt
cd ../ && cargo +nightly fmt

View file

@ -6,21 +6,14 @@ use rocket::{
serde::json::Json,
};
use nevmes_core::{
contact,
i2p,
message,
models,
monero,
proof,
reqres,
};
use nevmes_core::*;
use nevmes_market::*;
// JSON APIs exposed over i2p
/// Get payment API version
///
/// Protected: false
/// Protected: true
///
/// This also functions as a health check
#[get("/version")]
@ -28,6 +21,21 @@ pub async fn get_version(_jwp: proof::PaymentProof) -> Custom<Json<reqres::XmrRp
Custom(Status::Ok, Json(monero::get_version().await))
}
#[get("/test")]
pub async fn test() -> Custom<Json<models::User>> {
let mut v: Vec<String> = Vec::new();
v.push("t1".to_string());
v.push("t2".to_string());
v.push("t3".to_string());
let u: models::User = models::User {
uid: v.remove(0),
xmr_address: v.remove(0),
name: v.remove(0),
};
Custom(Status::Ok, Json(u))
}
/// If i2p not in the state of rejecting tunnels this will return `open: true`
///
/// Protected: false
@ -81,3 +89,20 @@ pub async fn gen_jwp(proof: Json<proof::TxProof>) -> Custom<Json<reqres::Jwp>> {
let jwp = proof::create_jwp(&proof).await;
Custom(Status::Ok, Json(reqres::Jwp { jwp }))
}
// NEVMES Market APIs
//-----------------------------------------------
/// Get all products by passing vendor address
///
/// Protected: true
#[get("/products")]
pub async fn get_products(
_jwp: proof::PaymentProof,
) -> Custom<Json<Vec<models::Product>>> {
let m_products: Vec<models::Product> = product::find_all();
Custom(
Status::Ok,
Json(m_products),
)
}

View file

@ -58,4 +58,6 @@ async fn rocket() -> _ {
.mount("/share", routes![controller::share_contact_info])
.mount("/i2p", routes![controller::get_i2p_status])
.mount("/xmr/rpc", routes![controller::get_version])
.mount("/market", routes![controller::get_products])
.mount("/test", routes![controller::test])
}