mirror of
https://github.com/creating2morrow/neveko.git
synced 2024-12-22 11:39:22 +00:00
v0.1.0-beta
This commit is contained in:
parent
b94802711c
commit
57e2be6384
30 changed files with 399 additions and 38 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -1932,7 +1932,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko"
|
name = "neveko"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"neveko_auth",
|
"neveko_auth",
|
||||||
|
@ -1946,7 +1946,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_auth"
|
name = "neveko_auth"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log 0.4.19",
|
"log 0.4.19",
|
||||||
|
@ -1956,7 +1956,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_contact"
|
name = "neveko_contact"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log 0.4.19",
|
"log 0.4.19",
|
||||||
|
@ -1966,7 +1966,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -1993,7 +1993,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_gui"
|
name = "neveko_gui"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
@ -2017,7 +2017,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_market"
|
name = "neveko_market"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -2028,7 +2028,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_message"
|
name = "neveko_message"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log 0.4.19",
|
"log 0.4.19",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko"
|
name = "neveko"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
@ -41,6 +41,7 @@ NEVidebla-EKOnomia (invisible economy)
|
||||||
* just remember to put cli password in the original window, not the log file window
|
* just remember to put cli password in the original window, not the log file window
|
||||||
* https://stackoverflow.com/questions/6674327/redirect-all-output-to-file-in-bash
|
* https://stackoverflow.com/questions/6674327/redirect-all-output-to-file-in-bash
|
||||||
* gui built with rust [egui](https://docs.rs/egui/latest/egui/)
|
* gui built with rust [egui](https://docs.rs/egui/latest/egui/)
|
||||||
|
* darknet release server links are located at: http://c2m66oddrzozztxyzjegbdwtgbeiibq5vz2tpchmqamrzcahcfoq.b32.i2p/index.txt
|
||||||
|
|
||||||
## Installation Mananger
|
## Installation Mananger
|
||||||
|
|
||||||
|
@ -111,5 +112,8 @@ most of the complex logic stays in neveko-core, exported from [lib.rs](./neveko-
|
||||||
|
|
||||||
## Donations
|
## Donations
|
||||||
|
|
||||||
This is just a hobby project but if anything here is useful donations are much appreciated!
|
This is a research project as of v0.1.0-beta but if anything here is useful donations are much appreciated!
|
||||||
No need to send xmr, donating time is fine. Just test code if you can and open issues. Thanks!
|
Features and bug fixes aren't guaranteed by donations but they will supply coffee for devs on
|
||||||
|
sleepless nights!
|
||||||
|
|
||||||
|
87TzQS4g6mN4oAcEhcnEHGCxw9bFwMXR8WHJEEZoCd7tPHgcH3NsiCF5FSWSkKYVa7EYJjuosPZBiNAh9LqHaRSiBUhsAcC
|
||||||
|
|
BIN
assets/customer_manage_orders.png
Normal file
BIN
assets/customer_manage_orders.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
BIN
assets/msig_management.png
Normal file
BIN
assets/msig_management.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 109 KiB |
BIN
assets/neveko-market_main.png
Normal file
BIN
assets/neveko-market_main.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
BIN
assets/vendor_manage_orders.png
Normal file
BIN
assets/vendor_manage_orders.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
BIN
assets/view_vendors.png
Normal file
BIN
assets/view_vendors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
28
docs/man.md
28
docs/man.md
|
@ -67,4 +67,30 @@
|
||||||
|
|
||||||
## Market
|
## Market
|
||||||
|
|
||||||
WIP
|
![market main view](../assets/neveko-market_main.png)
|
||||||
|
|
||||||
|
* neveko market allows 3 i2p users to create an order
|
||||||
|
* first you need to have a contact in vendor `enabled` mode
|
||||||
|
* vendors must also have products in stock
|
||||||
|
|
||||||
|
![vendor order management](../assets/vendor_manage_orders.png)
|
||||||
|
|
||||||
|
* here vendors can upload delivery info
|
||||||
|
* all other functionality including payment is automated
|
||||||
|
* funds will need to manually swept from neveko for now
|
||||||
|
|
||||||
|
![view vendors](../assets/view_vendors.png)
|
||||||
|
|
||||||
|
* don't forget to `check status` often
|
||||||
|
* expired jwp will cause errors when orchestrated multisig operations
|
||||||
|
|
||||||
|
![customer order management](../assets/customer_manage_orders.png)
|
||||||
|
|
||||||
|
* customers are responsible for orchestrated multisig information exchange with `MSIG`
|
||||||
|
* orders can also be disputed or cancelled from here
|
||||||
|
|
||||||
|
![create_jwp](../assets/msig_management.png)
|
||||||
|
|
||||||
|
* burden of multisig is on the customer
|
||||||
|
* click the msig step and use `check` to cycle to the next step
|
||||||
|
* upon delivery don't forget to `release txset`
|
||||||
|
|
4
neveko-auth/Cargo.lock
generated
4
neveko-auth/Cargo.lock
generated
|
@ -1195,7 +1195,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_auth"
|
name = "neveko_auth"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log 0.4.17",
|
"log 0.4.17",
|
||||||
|
@ -1205,7 +1205,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko_auth"
|
name = "neveko_auth"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
4
neveko-contact/Cargo.lock
generated
4
neveko-contact/Cargo.lock
generated
|
@ -1195,7 +1195,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_contact"
|
name = "neveko_contact"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log 0.4.17",
|
"log 0.4.17",
|
||||||
|
@ -1205,7 +1205,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko_contact"
|
name = "neveko_contact"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
2
neveko-core/Cargo.lock
generated
2
neveko-core/Cargo.lock
generated
|
@ -1195,7 +1195,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
@ -148,6 +148,13 @@ pub struct Args {
|
||||||
default_value = "false"
|
default_value = "false"
|
||||||
)]
|
)]
|
||||||
pub clear_fts: bool,
|
pub clear_fts: bool,
|
||||||
|
/// Remove all disputes from db on app startup
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "this will clear disputes from the database",
|
||||||
|
default_value = "false"
|
||||||
|
)]
|
||||||
|
pub clear_disputes: bool,
|
||||||
/// Manually configure i2p
|
/// Manually configure i2p
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db,
|
db,
|
||||||
models::*,
|
models::*,
|
||||||
|
monero,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
use log::{
|
use log::{
|
||||||
|
@ -24,6 +27,24 @@ pub fn create(d: Json<Dispute>) -> Dispute {
|
||||||
let s = db::Interface::open();
|
let s = db::Interface::open();
|
||||||
let k = &d.did;
|
let k = &d.did;
|
||||||
db::Interface::write(&s.env, &s.handle, k, &Dispute::to_db(&new_dispute));
|
db::Interface::write(&s.env, &s.handle, k, &Dispute::to_db(&new_dispute));
|
||||||
|
// in order to retrieve all orders, write keys to with dl
|
||||||
|
let list_key = crate::DISPUTE_LIST_DB_KEY;
|
||||||
|
let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key));
|
||||||
|
if r == utils::empty_string() {
|
||||||
|
debug!("creating dispute index");
|
||||||
|
}
|
||||||
|
let dispute_list = [String::from(&r), String::from(&f_did)].join(",");
|
||||||
|
debug!(
|
||||||
|
"writing dispute index {} for id: {}",
|
||||||
|
dispute_list, list_key
|
||||||
|
);
|
||||||
|
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &dispute_list);
|
||||||
|
// restart the dispute aut-settle thread
|
||||||
|
let cleared = is_dispute_clear(r);
|
||||||
|
if !cleared {
|
||||||
|
debug!("restarting dispute auto-settle");
|
||||||
|
utils::restart_dispute_auto_settle();
|
||||||
|
}
|
||||||
new_dispute
|
new_dispute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,3 +58,183 @@ pub fn find(did: &String) -> Dispute {
|
||||||
}
|
}
|
||||||
Dispute::from_db(String::from(did), r)
|
Dispute::from_db(String::from(did), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lookup all disputes
|
||||||
|
pub fn find_all() -> Vec<Dispute> {
|
||||||
|
let d_s = db::Interface::open();
|
||||||
|
let d_list_key = crate::DISPUTE_LIST_DB_KEY;
|
||||||
|
let d_r = db::Interface::read(&d_s.env, &d_s.handle, &String::from(d_list_key));
|
||||||
|
if d_r == utils::empty_string() {
|
||||||
|
error!("dispute index not found");
|
||||||
|
}
|
||||||
|
let d_v_did = d_r.split(",");
|
||||||
|
let d_v: Vec<String> = d_v_did.map(|s| String::from(s)).collect();
|
||||||
|
let mut disputes: Vec<Dispute> = Vec::new();
|
||||||
|
for o in d_v {
|
||||||
|
let dispute: Dispute = find(&o);
|
||||||
|
if dispute.did != utils::empty_string() {
|
||||||
|
disputes.push(dispute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disputes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispute deletion
|
||||||
|
pub fn delete(did: &String) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
db::Interface::delete(&s.env, &s.handle, &String::from(did))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered on DISPUTE_LAST_CHECK_DB_KEY.
|
||||||
|
///
|
||||||
|
/// If the current UNIX timestamp is less than the
|
||||||
|
///
|
||||||
|
/// creation date of the dispute plus the one week
|
||||||
|
///
|
||||||
|
/// grace period then the dispute is auto-settled.
|
||||||
|
pub async fn settle_dispute() {
|
||||||
|
let tick: std::sync::mpsc::Receiver<()> =
|
||||||
|
schedule_recv::periodic_ms(crate::DISPUTE_CHECK_INTERVAL);
|
||||||
|
loop {
|
||||||
|
debug!("running dispute auto-settle thread");
|
||||||
|
tick.recv().unwrap();
|
||||||
|
let s = db::Interface::open();
|
||||||
|
let list_key = crate::DISPUTE_LIST_DB_KEY;
|
||||||
|
let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key));
|
||||||
|
if r == utils::empty_string() {
|
||||||
|
info!("dispute index not found");
|
||||||
|
}
|
||||||
|
let v_mid = r.split(",");
|
||||||
|
let d_vec: Vec<String> = v_mid.map(|s| String::from(s)).collect();
|
||||||
|
debug!("dispute contents: {:#?}", d_vec);
|
||||||
|
let cleared = is_dispute_clear(r);
|
||||||
|
if cleared {
|
||||||
|
// index was created but cleared
|
||||||
|
info!("terminating dispute auto-settle thread");
|
||||||
|
db::Interface::delete(&s.env, &s.handle, list_key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for d in d_vec {
|
||||||
|
let dispute: Dispute = find(&d);
|
||||||
|
if dispute.did != utils::empty_string() {
|
||||||
|
let now = chrono::offset::Utc::now().timestamp();
|
||||||
|
let settle_date = dispute.created + crate::DISPUTE_AUTO_SETTLE as i64;
|
||||||
|
if settle_date > now {
|
||||||
|
let wallet_name = String::from(dispute.orid);
|
||||||
|
let wallet_password = utils::empty_string();
|
||||||
|
monero::open_wallet(&wallet_name, &wallet_password).await;
|
||||||
|
let signed = monero::sign_multisig(dispute.tx_set).await;
|
||||||
|
let submit = monero::submit_multisig(signed.result.tx_data_hex).await;
|
||||||
|
monero::close_wallet(&wallet_name, &wallet_password).await;
|
||||||
|
if submit.result.tx_hash_list.is_empty() {
|
||||||
|
error!("could not broadcast txset for dispute: {}", &dispute.did);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// remove the dispute from the db
|
||||||
|
remove_from_auto_settle(dispute.did);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dispute_clear(r: String) -> bool {
|
||||||
|
let v_mid = r.split(",");
|
||||||
|
let v: Vec<String> = v_mid.map(|s| String::from(s)).collect();
|
||||||
|
debug!("dispute index contents: {:#?}", v);
|
||||||
|
let limit = v.len() <= 1;
|
||||||
|
if !limit {
|
||||||
|
return v.len() >= 2
|
||||||
|
&& v[v.len() - 1] == utils::empty_string()
|
||||||
|
&& v[0] == utils::empty_string();
|
||||||
|
} else {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// clear dispute from index
|
||||||
|
fn remove_from_auto_settle(did: String) {
|
||||||
|
info!("removing id {} from disputes", &did);
|
||||||
|
let s = db::Interface::open();
|
||||||
|
let list_key = crate::DISPUTE_LIST_DB_KEY;
|
||||||
|
let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key));
|
||||||
|
if r == utils::empty_string() {
|
||||||
|
debug!("dispute list index is empty");
|
||||||
|
}
|
||||||
|
let pre_v_fts = r.split(",");
|
||||||
|
let v: Vec<String> = pre_v_fts
|
||||||
|
.map(|s| {
|
||||||
|
if s != &did {
|
||||||
|
String::from(s)
|
||||||
|
} else {
|
||||||
|
utils::empty_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let dispute_list = v.join(",");
|
||||||
|
debug!(
|
||||||
|
"writing dipsute index {} for id: {}",
|
||||||
|
dispute_list, list_key
|
||||||
|
);
|
||||||
|
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &dispute_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes POST /market/dispute/create
|
||||||
|
///
|
||||||
|
/// cancelling the order on the vendor side.
|
||||||
|
///
|
||||||
|
/// Customer needs to verify the response and update their lmdb.
|
||||||
|
///
|
||||||
|
/// see `cancel_order`
|
||||||
|
async fn transmit_dispute_request(
|
||||||
|
contact: &String,
|
||||||
|
jwp: &String,
|
||||||
|
request: &Dispute,
|
||||||
|
) -> Result<Dispute, Box<dyn Error>> {
|
||||||
|
info!("executing transmit_dispute_request");
|
||||||
|
let host = utils::get_i2p_http_proxy();
|
||||||
|
let proxy = reqwest::Proxy::http(&host)?;
|
||||||
|
let client = reqwest::Client::builder().proxy(proxy).build();
|
||||||
|
match client?
|
||||||
|
.post(format!("http://{}/market/dispute/create", contact))
|
||||||
|
.header("proof", jwp)
|
||||||
|
.json(&request)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(response) => {
|
||||||
|
let res = response.json::<Dispute>().await;
|
||||||
|
debug!("dispute response: {:?}", res);
|
||||||
|
match res {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
_ => Ok(Default::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to create a dispute due to: {:?}", e);
|
||||||
|
Ok(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A decomposition trigger for the dispute request so that the logic
|
||||||
|
///
|
||||||
|
/// can be executed from the gui.
|
||||||
|
pub async fn trigger_dispute_request(contact: &String, dispute: &Dispute) -> Dispute {
|
||||||
|
info!("executing trigger_dispute_request");
|
||||||
|
let s = db::Interface::async_open().await;
|
||||||
|
let k = format!("{}-{}", crate::FTS_JWP_DB_KEY, &contact);
|
||||||
|
let jwp = db::Interface::async_read(&s.env, &s.handle, &k).await;
|
||||||
|
let dispute = transmit_dispute_request(contact, &jwp, dispute).await;
|
||||||
|
// handle a failure to create dispute
|
||||||
|
if dispute.is_err() {
|
||||||
|
error!("failed to create dispute");
|
||||||
|
return Default::default();
|
||||||
|
}
|
||||||
|
dispute.unwrap_or(Default::default())
|
||||||
|
}
|
||||||
|
|
|
@ -28,10 +28,12 @@ pub const ORDER_DB_KEY: &str = "o";
|
||||||
pub const PRODUCT_DB_KEY: &str = "p";
|
pub const PRODUCT_DB_KEY: &str = "p";
|
||||||
pub const USER_DB_KEY: &str = "u";
|
pub const USER_DB_KEY: &str = "u";
|
||||||
pub const CONTACT_LIST_DB_KEY: &str = "cl";
|
pub const CONTACT_LIST_DB_KEY: &str = "cl";
|
||||||
|
pub const DISPUTE_LIST_DB_KEY: &str = "dl";
|
||||||
pub const MESSAGE_LIST_DB_KEY: &str = "ml";
|
pub const MESSAGE_LIST_DB_KEY: &str = "ml";
|
||||||
pub const ORDER_LIST_DB_KEY: &str = "ol";
|
pub const ORDER_LIST_DB_KEY: &str = "ol";
|
||||||
pub const PRODUCT_LIST_DB_KEY: &str = "pl";
|
pub const PRODUCT_LIST_DB_KEY: &str = "pl";
|
||||||
pub const RX_MESSAGE_DB_KEY: &str = "rx";
|
pub const RX_MESSAGE_DB_KEY: &str = "rx";
|
||||||
|
pub const DISPUTE_LAST_CHECK_DB_KEY: &str = "dlc";
|
||||||
pub const FTS_DB_KEY: &str = "fts";
|
pub const FTS_DB_KEY: &str = "fts";
|
||||||
pub const CUSTOMER_ORDER_LIST_DB_KEY: &str = "olc";
|
pub const CUSTOMER_ORDER_LIST_DB_KEY: &str = "olc";
|
||||||
pub const MEDIATOR_DB_KEY: &str = "med8";
|
pub const MEDIATOR_DB_KEY: &str = "med8";
|
||||||
|
@ -68,4 +70,9 @@ pub const I2P_ZERO_RELEASH_HASH: &str =
|
||||||
|
|
||||||
pub const LMDB_MAPSIZE: u64 = 1 * 1024 * 1024 * 1024;
|
pub const LMDB_MAPSIZE: u64 = 1 * 1024 * 1024 * 1024;
|
||||||
pub const I2P_CONNECTIVITY_CHECK_INTERVAL: u32 = 600000;
|
pub const I2P_CONNECTIVITY_CHECK_INTERVAL: u32 = 600000;
|
||||||
|
pub const FTS_RETRY_INTERVAL: u32 = 60000;
|
||||||
|
/// There is a one week grace period for manual intervention of disputes
|
||||||
|
pub const DISPUTE_AUTO_SETTLE: u32 = 1000 * 60 * 60 * 24 * 7;
|
||||||
|
/// Daily dispute auto-settle check interval
|
||||||
|
pub const DISPUTE_CHECK_INTERVAL: u32 = 1000 * 60 * 60 * 24;
|
||||||
// DO NOT EDIT BELOW THIS LINE
|
// DO NOT EDIT BELOW THIS LINE
|
||||||
|
|
|
@ -422,7 +422,7 @@ fn remove_from_fts(mid: String) {
|
||||||
///
|
///
|
||||||
/// failed-to-send message.
|
/// failed-to-send message.
|
||||||
pub async fn retry_fts() {
|
pub async fn retry_fts() {
|
||||||
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000);
|
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(crate::FTS_RETRY_INTERVAL);
|
||||||
loop {
|
loop {
|
||||||
debug!("running retry failed-to-send thread");
|
debug!("running retry failed-to-send thread");
|
||||||
tick.recv().unwrap();
|
tick.recv().unwrap();
|
||||||
|
|
|
@ -238,10 +238,13 @@ pub async fn sign_and_submit_multisig(
|
||||||
tx_data_hex: &String,
|
tx_data_hex: &String,
|
||||||
) -> reqres::XmrRpcSubmitMultisigResponse {
|
) -> reqres::XmrRpcSubmitMultisigResponse {
|
||||||
info!("signing and submitting multisig");
|
info!("signing and submitting multisig");
|
||||||
|
let wallet_password = utils::empty_string();
|
||||||
|
monero::open_wallet(&orid, &wallet_password).await;
|
||||||
let r_sign: reqres::XmrRpcSignMultisigResponse =
|
let r_sign: reqres::XmrRpcSignMultisigResponse =
|
||||||
monero::sign_multisig(String::from(tx_data_hex)).await;
|
monero::sign_multisig(String::from(tx_data_hex)).await;
|
||||||
let r_submit: reqres::XmrRpcSubmitMultisigResponse =
|
let r_submit: reqres::XmrRpcSubmitMultisigResponse =
|
||||||
monero::submit_multisig(r_sign.result.tx_data_hex).await;
|
monero::submit_multisig(r_sign.result.tx_data_hex).await;
|
||||||
|
monero::close_wallet(&orid, &wallet_password).await;
|
||||||
if r_submit.result.tx_hash_list.is_empty() {
|
if r_submit.result.tx_hash_list.is_empty() {
|
||||||
error!("unable to submit payment for order: {}", orid);
|
error!("unable to submit payment for order: {}", orid);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
args,
|
args,
|
||||||
contact,
|
contact,
|
||||||
db,
|
db,
|
||||||
|
dispute,
|
||||||
gpg,
|
gpg,
|
||||||
i2p,
|
i2p,
|
||||||
message,
|
message,
|
||||||
|
@ -348,6 +349,16 @@ pub fn order_to_json(o: &reqres::OrderRequest) -> Json<reqres::OrderRequest> {
|
||||||
Json(r_order)
|
Json(r_order)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dispute_to_json(d: &models::Dispute) -> Json<models::Dispute> {
|
||||||
|
let dispute: models::Dispute = models::Dispute {
|
||||||
|
created: d.created,
|
||||||
|
did: String::from(&d.did),
|
||||||
|
orid: String::from(&d.orid),
|
||||||
|
tx_set: String::from(&d.tx_set),
|
||||||
|
};
|
||||||
|
Json(dispute)
|
||||||
|
}
|
||||||
|
|
||||||
/// Instead of putting `String::from("")`
|
/// Instead of putting `String::from("")`
|
||||||
pub fn empty_string() -> String {
|
pub fn empty_string() -> String {
|
||||||
String::from("")
|
String::from("")
|
||||||
|
@ -537,6 +548,9 @@ pub async fn start_up() {
|
||||||
if args.clear_fts {
|
if args.clear_fts {
|
||||||
clear_fts();
|
clear_fts();
|
||||||
}
|
}
|
||||||
|
if args.clear_disputes {
|
||||||
|
clear_disputes();
|
||||||
|
}
|
||||||
gen_signing_keys();
|
gen_signing_keys();
|
||||||
if !is_using_remote_node() {
|
if !is_using_remote_node() {
|
||||||
monero::start_daemon();
|
monero::start_daemon();
|
||||||
|
@ -565,9 +579,11 @@ pub async fn start_up() {
|
||||||
gen_app_gpg().await;
|
gen_app_gpg().await;
|
||||||
gen_app_wallet(&wallet_password).await;
|
gen_app_wallet(&wallet_password).await;
|
||||||
start_gui();
|
start_gui();
|
||||||
|
// start async background tasks here
|
||||||
{
|
{
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
message::retry_fts().await;
|
message::retry_fts().await;
|
||||||
|
dispute::settle_dispute().await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
info!("{} - neveko is online", env);
|
info!("{} - neveko is online", env);
|
||||||
|
@ -608,11 +624,25 @@ pub fn restart_retry_fts() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We can restart dispute auto-settle from since it gets terminated when empty
|
||||||
|
pub fn restart_dispute_auto_settle() {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispute::settle_dispute().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Called on app startup if `--clear-fts` flag is passed.
|
/// Called on app startup if `--clear-fts` flag is passed.
|
||||||
fn clear_fts() {
|
fn clear_fts() {
|
||||||
info!("clear fts");
|
info!("clear fts");
|
||||||
let s = db::Interface::open();
|
let s = db::Interface::open();
|
||||||
db::Interface::delete(&s.env, &s.handle, "fts");
|
db::Interface::delete(&s.env, &s.handle, crate::FTS_DB_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called on app startup if `--clear-dispute` flag is passed.
|
||||||
|
fn clear_disputes() {
|
||||||
|
info!("clear_disputes");
|
||||||
|
let s = db::Interface::open();
|
||||||
|
db::Interface::delete(&s.env, &s.handle, crate::DISPUTE_LIST_DB_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy temp files
|
/// Destroy temp files
|
||||||
|
|
4
neveko-gui/Cargo.lock
generated
4
neveko-gui/Cargo.lock
generated
|
@ -2415,7 +2415,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -2442,7 +2442,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_gui"
|
name = "neveko_gui"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko_gui"
|
name = "neveko_gui"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
authors = ["emilk", "creating2morrow"]
|
authors = ["emilk", "creating2morrow"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
|
@ -116,6 +116,8 @@ pub struct MarketApp {
|
||||||
submit_txset_rx: Receiver<bool>,
|
submit_txset_rx: Receiver<bool>,
|
||||||
cancel_request_tx: Sender<models::Order>,
|
cancel_request_tx: Sender<models::Order>,
|
||||||
cancel_request_rx: Receiver<models::Order>,
|
cancel_request_rx: Receiver<models::Order>,
|
||||||
|
dispute_request_tx: Sender<models::Dispute>,
|
||||||
|
dispute_request_rx: Receiver<models::Dispute>,
|
||||||
// ship_request_tx: Sender<models::Order>,
|
// ship_request_tx: Sender<models::Order>,
|
||||||
// ship_request_rx: Receiver<models::Order>,
|
// ship_request_rx: Receiver<models::Order>,
|
||||||
upload_dinfo_tx: Sender<bool>,
|
upload_dinfo_tx: Sender<bool>,
|
||||||
|
@ -144,6 +146,7 @@ impl Default for MarketApp {
|
||||||
let (submit_order_tx, submit_order_rx) = std::sync::mpsc::channel();
|
let (submit_order_tx, submit_order_rx) = std::sync::mpsc::channel();
|
||||||
// let (ship_request_tx, ship_request_rx) = std::sync::mpsc::channel();
|
// let (ship_request_tx, ship_request_rx) = std::sync::mpsc::channel();
|
||||||
let (cancel_request_tx, cancel_request_rx) = std::sync::mpsc::channel();
|
let (cancel_request_tx, cancel_request_rx) = std::sync::mpsc::channel();
|
||||||
|
let (dispute_request_tx, dispute_request_rx) = std::sync::mpsc::channel();
|
||||||
let (our_prepare_info_tx, our_prepare_info_rx) = std::sync::mpsc::channel();
|
let (our_prepare_info_tx, our_prepare_info_rx) = std::sync::mpsc::channel();
|
||||||
let (our_make_info_tx, our_make_info_rx) = std::sync::mpsc::channel();
|
let (our_make_info_tx, our_make_info_rx) = std::sync::mpsc::channel();
|
||||||
let (order_xmr_address_tx, order_xmr_address_rx) = std::sync::mpsc::channel();
|
let (order_xmr_address_tx, order_xmr_address_rx) = std::sync::mpsc::channel();
|
||||||
|
@ -219,6 +222,8 @@ impl Default for MarketApp {
|
||||||
s_order: Default::default(),
|
s_order: Default::default(),
|
||||||
cancel_request_rx,
|
cancel_request_rx,
|
||||||
cancel_request_tx,
|
cancel_request_tx,
|
||||||
|
dispute_request_rx,
|
||||||
|
dispute_request_tx,
|
||||||
// ship_request_rx,
|
// ship_request_rx,
|
||||||
// ship_request_tx,
|
// ship_request_tx,
|
||||||
submit_order_rx,
|
submit_order_rx,
|
||||||
|
@ -341,6 +346,14 @@ impl eframe::App for MarketApp {
|
||||||
self.is_loading = false;
|
self.is_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(disputed) = self.dispute_request_rx.try_recv() {
|
||||||
|
if disputed.tx_set != utils::empty_string() {
|
||||||
|
log::info!("dispute in progress");
|
||||||
|
}
|
||||||
|
self.is_loading = false;
|
||||||
|
self.is_customer_viewing_orders = false;
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(finalized) = self.submit_txset_rx.try_recv() {
|
if let Ok(finalized) = self.submit_txset_rx.try_recv() {
|
||||||
if !finalized {
|
if !finalized {
|
||||||
log::error!("failure to finalize shipment please contact vendor")
|
log::error!("failure to finalize shipment please contact vendor")
|
||||||
|
@ -734,10 +747,6 @@ impl eframe::App for MarketApp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// ui.horizontal(|ui| {
|
|
||||||
// ui.label("Create Dispute: \t\t");
|
|
||||||
// if ui.button("Dispute").clicked() {}
|
|
||||||
// });
|
|
||||||
ui.label("\n");
|
ui.label("\n");
|
||||||
if ui.button("Exit").clicked() {
|
if ui.button("Exit").clicked() {
|
||||||
self.is_managing_multisig = false;
|
self.is_managing_multisig = false;
|
||||||
|
@ -816,6 +825,7 @@ impl eframe::App for MarketApp {
|
||||||
.column(Column::auto())
|
.column(Column::auto())
|
||||||
.column(Column::auto())
|
.column(Column::auto())
|
||||||
.column(Column::auto())
|
.column(Column::auto())
|
||||||
|
.column(Column::auto())
|
||||||
.min_scrolled_height(0.0);
|
.min_scrolled_height(0.0);
|
||||||
|
|
||||||
table
|
table
|
||||||
|
@ -838,6 +848,9 @@ impl eframe::App for MarketApp {
|
||||||
header.col(|ui| {
|
header.col(|ui| {
|
||||||
ui.strong("");
|
ui.strong("");
|
||||||
});
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("");
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.body(|mut body| {
|
.body(|mut body| {
|
||||||
for o in &self.customer_orders {
|
for o in &self.customer_orders {
|
||||||
|
@ -898,6 +911,25 @@ impl eframe::App for MarketApp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.style_mut().wrap = Some(false);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Dispute").clicked() {
|
||||||
|
let vendor_prefix = String::from(crate::GUI_OVL_DB_KEY);
|
||||||
|
let vendor = utils::search_gui_db(
|
||||||
|
vendor_prefix,
|
||||||
|
self.m_order.orid.clone(),
|
||||||
|
);
|
||||||
|
self.is_loading = true;
|
||||||
|
create_dispute_req(
|
||||||
|
&self.m_order.orid.clone(),
|
||||||
|
ctx.clone(),
|
||||||
|
self.dispute_request_tx.clone(),
|
||||||
|
&vendor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1003,7 +1035,8 @@ impl eframe::App for MarketApp {
|
||||||
.labelled_by(delivery_info.id);
|
.labelled_by(delivery_info.id);
|
||||||
});
|
});
|
||||||
if self.new_order.orid != utils::empty_string()
|
if self.new_order.orid != utils::empty_string()
|
||||||
&& self.upload_dinfo_str != utils::empty_string() {
|
&& self.upload_dinfo_str != utils::empty_string()
|
||||||
|
{
|
||||||
if ui.button("Trigger NASR").clicked() {
|
if ui.button("Trigger NASR").clicked() {
|
||||||
// upload delivery info
|
// upload delivery info
|
||||||
let dinfo_str = String::from(&self.upload_dinfo_str);
|
let dinfo_str = String::from(&self.upload_dinfo_str);
|
||||||
|
@ -2402,3 +2435,53 @@ fn upload_dinfo_req(dinfo: Vec<u8>, orid: String, ctx: egui::Context, tx: Sender
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_dispute_req(
|
||||||
|
orid: &String,
|
||||||
|
ctx: egui::Context,
|
||||||
|
tx: Sender<models::Dispute>,
|
||||||
|
contact: &String,
|
||||||
|
) {
|
||||||
|
let d_orid: String = String::from(orid);
|
||||||
|
let a_contact: String = String::from(contact);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
log::info!("async create_dispute_req");
|
||||||
|
// generate address for refund
|
||||||
|
let wallet_password =
|
||||||
|
std::env::var(neveko_core::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password"));
|
||||||
|
let wallet_name = String::from(neveko_core::APP_NAME);
|
||||||
|
monero::open_wallet(&wallet_name, &wallet_password).await;
|
||||||
|
let address_res = monero::get_address().await;
|
||||||
|
monero::close_wallet(&wallet_name, &wallet_password).await;
|
||||||
|
// generate a txset for the mediator
|
||||||
|
let wallet_password = utils::empty_string();
|
||||||
|
monero::open_wallet(&d_orid, &wallet_password).await;
|
||||||
|
let transfer = monero::sweep_all(String::from(address_res.result.address)).await;
|
||||||
|
monero::close_wallet(&d_orid, &wallet_password).await;
|
||||||
|
if transfer.result.multisig_txset.is_empty() {
|
||||||
|
log::error!("could not create txset");
|
||||||
|
let _ = tx.send(Default::default());
|
||||||
|
ctx.request_repaint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let dispute: models::Dispute = models::Dispute {
|
||||||
|
orid: String::from(&d_orid),
|
||||||
|
tx_set: transfer.result.multisig_txset,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = dispute::trigger_dispute_request(&a_contact, &dispute).await;
|
||||||
|
if res.created != 0 {
|
||||||
|
// cancel the order and write the dispute to the db
|
||||||
|
let wallet_password = std::env::var(neveko_core::MONERO_WALLET_PASSWORD)
|
||||||
|
.unwrap_or(String::from("password"));
|
||||||
|
monero::open_wallet(&String::from(neveko_core::APP_NAME), &wallet_password).await;
|
||||||
|
let pre_sign = monero::sign(String::from(&d_orid)).await;
|
||||||
|
monero::close_wallet(&String::from(neveko_core::APP_NAME), &wallet_password).await;
|
||||||
|
order::cancel_order(&d_orid, &pre_sign.result.signature).await;
|
||||||
|
let j_dispute = utils::dispute_to_json(&res);
|
||||||
|
dispute::create(j_dispute);
|
||||||
|
let _ = tx.send(res);
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"neveko-gui-v0.4.8-alpha",
|
"neveko-gui-v0.1.0-beta",
|
||||||
options,
|
options,
|
||||||
Box::new(|cc| Box::new(neveko_gui::WrapApp::new(cc))),
|
Box::new(|cc| Box::new(neveko_gui::WrapApp::new(cc))),
|
||||||
)
|
)
|
||||||
|
|
4
neveko-market/Cargo.lock
generated
4
neveko-market/Cargo.lock
generated
|
@ -1244,7 +1244,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -1271,7 +1271,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_market"
|
name = "neveko_market"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko_market"
|
name = "neveko_market"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|
4
neveko-message/Cargo.lock
generated
4
neveko-message/Cargo.lock
generated
|
@ -1195,7 +1195,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_core"
|
name = "neveko_core"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -1222,7 +1222,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neveko_message"
|
name = "neveko_message"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log 0.4.17",
|
"log 0.4.17",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "neveko_message"
|
name = "neveko_message"
|
||||||
version = "0.4.8-alpha"
|
version = "0.1.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
@ -258,7 +258,7 @@ pub async fn finalize_order(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a dispute
|
/// Create a dispute
|
||||||
#[post("/create", data = "<dispute>")]
|
#[post("/dispute/create", data = "<dispute>")]
|
||||||
pub async fn create_dispute(
|
pub async fn create_dispute(
|
||||||
dispute: Json<models::Dispute>,
|
dispute: Json<models::Dispute>,
|
||||||
_jwp: proof::PaymentProof,
|
_jwp: proof::PaymentProof,
|
||||||
|
|
Loading…
Reference in a new issue