RoboSats / robosats

A simple and private bitcoin exchange
https://learn.robosats.com
GNU Affero General Public License v3.0
751 stars 146 forks source link

Add location to F2F payment option #760

Closed f321x closed 1 year ago

f321x commented 1 year ago

Is your feature request related to a problem? Please describe. Currently, if i understand correctly, the F2F payment option has no possibility to set a location (eg City or coordinates). This renders the otherwise private and useful payment method fully useless as the trade partner cannot see where the other side is located before accepting the order and paying the bond. In Bisq for example you can see the location of the p2p trade before accepting the offer, so you can only accept it if the location is reachable/useful. This is probably also the reason why there are so few F2F offers active.

Describe the solution you'd like Add a field to enter the location when creating an offer with F2F payment method and show the location in the orderbook either always (maybe if only F2F is accepted) or for example on hover over the F2F icon or when looking at the offer details.

Additional context For example see Bisq where this is being done for a long time. Bildschirmfoto vom 2023-07-29 23-27-18

Reckless-Satoshi commented 1 year ago

Hey @f321x thank you for bringing this up.

It is already possible to do something very very close to what you propose.

How it can be done now

The payment methods field is a free text entry. The autocomplete box a faster way to enter your freestyle text, then the payment methods icons are simply parsed from the string. One could write "Face to face Amsterdam" as a payment method. Or even select "F2F" to get the icon and then enter "Amsterdam" image image However, this is certainly... more of a hack than a feature :) It is not obvious for any user that the F2F method can be used this way (but I've certainly seen this a few times in the order book!). I think we should take F2F seriously and make it more clear.

How it can be improved

I was exploring ideas some time ago, but I never got to write them down or implement them. It would be great if:

  1. Selecting F2F opens a small pop up dialog with a tiled map.
  2. The user clicks on the map.
  3. The frontend adds a random jitter of about 10 Km (lat and lon) to make sure to anonymize a users who might click on top their own house (lol)
  4. The order book shows a map with the location of all the F2F orders posted.

This will only require the Order model to have 2 new optional fields: latitude and longitude. I think there might be ways we can even embed a very low resolution tiled map within the client. That way we do not include any dependency to third party APIs for positioning.

It's a very cool and exciting idea, a bit challenging but it might not even take too long to be implemented to be honest. It's mostly client side and those 2 new fields for Order .

Reckless-Satoshi commented 1 year ago

Okay, I think this is really a must :D Let's put a 1,000,000 Sats bounty on:

  1. implementing the client side light map component
  2. extracting lat/lon on click
  3. submitting anonymized lat/lon for F2F orders and,
  4. displaying all F2F orders over the canvas map in the Offers tab. Ieally if they show the same tooltip of the DepthChart on hover and take you to the order on click.
f321x commented 1 year ago

Cool didn't know the payment method field allows free text, actually elegant but a bit unintuitive.

Looked into implementing this (no promises), Leaflet is probably a lightweight and usable solution? https://leafletjs.com/examples/quick-start/

But i guess embedded maps are more pain- than useful as a low res GeoJson worldmap (borders & city names) is >20mb and resource intensive to render iiuc. Alternatives would be the openstreetmap api (a free/libre project), very detailed, fast (usable on Tor) and probably reliable. Or hosting kind of a map server on the stack and only serving the necessary tiles, probably also performant but more cumbersome to maintain (update mapfiles) and few benefits except independence.

Can you think of other ways of doing this, or which solution would you prefer?

Reckless-Satoshi commented 1 year ago

Indeed there are many options!

We could re-tile OpenStreetMap to a very coarse resolution and add it to our static folder. This way the Nodeapp and Android app will self-serve the map and those using the hosted client over TOR will be served the map from the coordinator.

Another cool and straight forward option might be using an SVG map and d3.js (this was suggested by GPT4). In fact, there seems to be an NPM library svgMAP That does something similar, has many features we don't need though. Maybe a map like this is sort of enough to click and get approximate lat/lon? Demo here: https://stephanwagner.me/create-world-map-charts-with-svgmap#svgMapDemoGDP I guess the SVG layer is possibly easy to edit and add points for the 10 biggest cities in each country (or something like that).

For more context, this is GPT4 full answer to how to create a lightweight map component for a ReactJS app:

Creating a Map component in ReactJS without external dependencies can be a bit challenging since most map libraries like Google Maps or Mapbox have their own API dependencies. However, we can create a simple map using SVG and D3.js, which is a JavaScript library for creating data visualizations. D3.js can also handle geographical data, which makes it possible to create a map.

Please note, this approach will not provide a feature-rich map like Google Maps or Mapbox, but it should be sufficient for a simple use case where you need to click on a map and get latitude and longitude.

Here's a step-by-step approach to creating a Map component in ReactJS:

  1. Create a new React component and import D3.js

First, create a new component for your map. You can call it MapComponent. Then, import D3.js into your component.

import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3';
  1. Use D3.js to draw your map

D3.js provides several methods for drawing maps. One of the simplest ways is to use the geoMercator projection, which represents the earth as a flat surface. You can use D3.js to draw a world map using a GeoJSON file that contains the geographical data of the world.

const MapComponent = () => {
  const ref = useRef();

  useEffect(() => {
    const svg = d3.select(ref.current);
    const projection = d3.geoMercator();
    const path = d3.geoPath().projection(projection);

    d3.json('/path-to-your-geojson-file.json').then((data) => {
      svg.selectAll('path')
        .data(data.features)
        .enter()
        .append('path')
        .attr('d', path);
    });
  }, []);

  return <svg ref={ref}></svg>;
};
  1. Add an event listener to the map

To get the latitude and longitude when a user clicks on the map, you can add an onclick event listener to the SVG paths. Inside the event handler, you can use the projection.invert method to convert the x and y coordinates of the mouse click event to latitude and longitude.

const MapComponent = () => {
  const ref = useRef();

  useEffect(() => {
    const svg = d3.select(ref.current);
    const projection = d3.geoMercator();
    const path = d3.geoPath().projection(projection);

    d3.json('/path-to-your-geojson-file.json').then((data) => {
      svg.selectAll('path')
        .data(data.features)
        .enter()
        .append('path')
        .attr('d', path)
        .on('click', function(event) {
          const [x, y] = d3.pointer(event);
          const [lng, lat] = projection.invert([x, y]);
          console.log(`Latitude: ${lat}, Longitude: ${lng}`);
        });
    });
  }, []);

  return <svg ref={ref}></svg>;
};

In this case, when you click on the map, the latitude and longitude of the click location will be logged to the console.

Remember, this is a very basic map and might not be accurate enough for some use cases. If you need a more accurate and feature-rich map, consider using a map library that has external dependencies.

It uses a geojson as input. For context, svgMAP 's map is 100KB. Here the shapes country by country https://github.com/StephanWagner/svgMap/blob/master/src/js/svgMap.js#L1342

KoalaSat commented 1 year ago

Hello there, I have experience with leaflet and maps in general, there are some free APIs you can use to display OpenStreetMap tiles. Using SVG is way too big or way too inaccurate. I'll investigate what are the best privacy friendly options for using OpenStreetMap (maybe there are proxies through Tor, who knows).

@Reckless-Satoshi I know Ieft my last rewards ticket left (sorry 🥲) but I think I can take care of this one now 😃

KoalaSat commented 1 year ago

There are ways to include your own tile server https://hub.docker.com/r/overv/openstreetmap-tile-server

Here is a super nice website about it https://switch2osm.org/serving-tiles/ , I'm only concerned about the system requirements.

Reckless-Satoshi commented 1 year ago

Here is a super nice website about it https://switch2osm.org/serving-tiles/ , I'm only concerned about the system requirements.

Nice resource! Indeed, it seems to be very intensive (specially if we intend the tile server to run on self-hosted nodes that are typically Umbrel / StartOS with low end hardware).

Serving tiles also seem to be a bit of an overkill given that we do not need high precision or contextual information such as street layout, names, elevation, etc. It suffices with high level boundaries of countries or regions and a point cloud that vaguely shows the location of orders at the ~continent scale.

KoalaSat commented 1 year ago

Sharing my answer in the chat:

Ah, my initial thought was something way more specific, like this kind of F2F apps for selling your 2nd hand stuff.

In that case yeah I think that the most specific level you can get is a city neighbourhoods, probably would be easy to get the top 50 as you say.

We dont even need to use a new library, Nivo already provides a svg map. I would focus on having all these files out of the build as external assets to avoid having huge sizes, Ill take a look and see whats the easiest way.

KoalaSat commented 1 year ago

Found this https://ahuseyn.github.io/interactive-svg-maps/ it looks like it contains almost all countries, what about this?

  1. The user selects F2F from the payment methods dropdown
  2. A popup with a world map appears and the user clicks on a country
  3. The map changes to a detailed view of the country
  4. The user picks the approximate location of his home.
  5. A 10km radius circle draws on the map
  6. The user confirms the location and the coordinates are associated to the order
Reckless-Satoshi commented 1 year ago
  • The user selects F2F from the payment methods dropdown

  • A popup with a world map appears and the user clicks on a country

  • The map changes to a detailed view of the country

  • The user picks the approximate location of his home.

  • A 10km radius circle draws on the map

  • The user confirms the location and the coordinates are associated to the order

This sounds just perfect! :rocket: The maps you found are great. Also very nice that they are MIT license!

KoalaSat commented 1 year ago

Found a problem with SVG, the image is not defined with world coordinates so there is no way to relate the position the user clicks on with a real world coordinate, the solution for that (and the implemented for nivo's map) is GeoJson.

I found this library with the entire world and all countries https://github.com/AshKyd/geojson-regions/tree/master the only issue is that we lost the regions and provinces, something that anyways we can't use with SVG. I'm open to any other sugestion or help finding out a better geojson for countries 😃

Reckless-Satoshi commented 1 year ago

I'm open to any other sugestion or help finding out a better geojson for countries 😃

I think this is very decent compromise. We can probably find or craft ourself geojson for internal borders for the countries that are most used in the future.

KoalaSat commented 1 year ago

Small update: I'm stoping the implemetation of nivo maps, looks like the transformation of the geojson to d3 transforms geo coodinates to x/y coordinates, plus it gets really dificult to obtain the exact point where the user clicks. I'll try out leaflet wich seems to be compatible with geojson.