Giter Site home page Giter Site logo

pretix-eth-payment-plugin's People

Contributors

a5net avatar davesque avatar dependabot[bot] avatar kvbik avatar ligi avatar mikulas-mrva avatar pauljickling avatar pedrouid avatar pipermerriam avatar rahul-kothari avatar raphaelm avatar vic-en avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pretix-eth-payment-plugin's Issues

production setup to readme

currently there is only a section "Development setup" - at some point we should add a section production setup

Update web3connect

currently our web3connect option offers walletconnect and metamask as options.
The new web3connect version offers more options - so we should update web3connect

Depiction of the payment option in the purchasing UI

When the user is presented with options to pay, the pretix-eth-payment-plugin displays "Ethereum Plugin".

image

I do not see where this can be changed in the Admin Settings for the plugin so it must be what is defined here in payment.py:

https://github.com/esPass/pretix-eth-payment-plugin/blob/1f7764601aceb83643c92619fbc88b349489ce39/pretix_eth/payment.py

I am thinking that it should read: "ETH or DAI" or perhaps "Pay directly with ETH or DAI".

Let me know if there is any impact of changing this to something more comprehensible to a purchasing user.

Expiration of exchange rates for ETH to prevent shorting

What was wrong?

Alice loads the order page when ETH is worth $100 each. The backend queries for the proper exchange rate and stores it in her session. This determines how much ETH she must pay for a ticket.

Alice now waits 12 hours, during which the price of ETH drops 10%. She is now able to purchase a ticket at a 10% discount since the backend will use the stored exchange rate from time she loaded the order page.

How can it be fixed?

Enforce an expiration on exchange rates for volatile currencies to ensure that there are bounds on how long someone can short the currency to purchase their devcon ticket. Something like 30 minutes should be sufficient to account for network congestion or even sending funds from an exchange.

This Checker doesn't work with contract wallets

exception starting pretix after installing the plugin

I just installed the plugin - before pretix was working fine - but afterwards I am getting this:

Watching for file changes with StatReloader
INFO 2019-05-17 18:42:51,423 django.utils.autoreload autoreload Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.5/threading.py", line 862, in run
    self._target(*self._args, **self._kwargs)
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/utils/autoreload.py", line 54, in wrapper
    fn(*args, **kwargs)
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/core/management/commands/runserver.py", line 120, in inner_run
    self.check_migrations()
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/core/management/base.py", line 453, in check_migrations
    executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/executor.py", line 18, in __init__
    self.loader = MigrationLoader(self.connection)
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/loader.py", line 49, in __init__
    self.build_graph()
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/loader.py", line 274, in build_graph
    raise exc
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/loader.py", line 248, in build_graph
    self.graph.validate_consistency()
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/graph.py", line 195, in validate_consistency
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/graph.py", line 195, in <listcomp>
    [n.raise_error() for n in self.node_map.values() if isinstance(n, DummyNode)]
  File "/home/ligi/git/pretix/src/env/lib/python3.5/site-packages/django/db/migrations/graph.py", line 58, in raise_error
    raise NodeNotFoundError(self.error_message, self.key, origin=self.origin)
django.db.migrations.exceptions.NodeNotFoundError: Migration pretix_eth.0001_initial dependencies reference nonexistent parent node ('pretixbase', '0109_auto_20190215_1700')

Help users protect their privacy

Make users aware if they do not want to associate their account with a conference - they need to use a mixer (and ideally later a checkbox [ ] use mixer)

Security: incoming transaction verification

the transactions should be better verified. Currently single services are queried. One thing to mitigate could be to query more services or in an ideal world even use a system like INCUBED to verify incoming transactions

In this process we might also want to use web3.py

DAI amounts are not correctly compared

What was wrong?

DAI values obtained from blockscout by the token provider are in DAI "wei" units (e.g. 2 DAI = 2 x 10^18 DAI wei). However, rate quotes are in whole DAI units.

How can it be fixed?

The rate quote functionality should ensure that its amount values are scaled appropriately for comparison with the token provider amount values.

Don't ask for tx-hash before payment method selected

image

this is wrong - we cannot ask for the transaction hash before the payment method is selected or the order is placed.
This bad flow was introduced with the security fixes for #8 - the problem is when fixing the problem there where obstacles to get an input field in the.

I do not think the user should enter the TX-Hash at all

I propose the following:
the user just selects the currency type:
image

then we show a QR (ERC-681) and a Web3Connect link (or js call) to trigger the payment

important is that the data-field gets a mandatory payment object ID - so we can prevent #8 and the sniping attack @pipermerriam pointed out

but this means in the beginning we can only take ETH and xDAI no DAI yet.
Later on (but IMHO not in wave 1 for DevCon) we should create a smart contract that accepts ETH and DAI with a function

EDIT: we can also take DAI by using the least significant bits of the value to encode the payment object ID (less than 5000WEI for DevCon - so in comparison to gas usage negligible)

Viewing pending orders in admin mode causes an error

This likely relates to the Ethereum Plugin, or possibly in pretix itself. This is tested with eth-payment-plugin 1.0.1.

I am able to view orders, but viewing pending orders from the Ethereum Plugin causes an error. This is from the pretix log at /var/pretix/data/logs/pretix.log:

Traceback (most recent call last):
  File "/var/pretix/venv/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/var/pretix/venv/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/var/pretix/venv/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/var/pretix/venv/lib/python3.7/site-packages/pretix/control/permissions.py", line 27, in wrapper
    return function(request, *args, **kw)
  File "/var/pretix/venv/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/var/pretix/venv/lib/python3.7/site-packages/django/views/generic/base.py", line 97, in dispatch
    return handler(request, *args, **kwargs)
  File "/var/pretix/venv/lib/python3.7/site-packages/django/views/generic/detail.py", line 107, in get
    context = self.get_context_data(object=self.object)
  File "/var/pretix/venv/lib/python3.7/site-packages/pretix/control/views/orders.py", line 203, in get_context_data
    p.html_info = (p.payment_provider.payment_control_render(self.request, p) or "").strip()
  File "/var/pretix/venv/lib/python3.7/site-packages/pretix_eth/payment.py", line 213, in payment_control_render
    'coin': request.session['payment_ethereum_fm_currency']}
  File "/var/pretix/venv/lib/python3.7/site-packages/django/contrib/sessions/backends/base.py", line 54, in __getitem__
    return self._session[key]
KeyError: 'payment_ethereum_fm_currency'```

Mismatch between payment_prepare and checkout_prepare

payment_prepare does not call _get_rates_checkout. Therefore, payment_ethereum_time and payment_ethereum_amount are not set on the session and execute_payment. checkout_prepare is correct, so the bug will only occur in the "secondary" payment flow, so only if a failed payment is retried later or if a payment method is switched.

KeyError 'currency_type'

had a pending payment. wanted to change from DAI to ETH.. decided to do it later, clicked cancel.


Request Method: | GET
-- | --
2.2.2
KeyError
'currency_type'
/var/pretix/venv/lib/python3.7/site-packages/pretix_eth/payment.py in payment_pending_render, line 221
/var/pretix/venv/bin/python3
3.7.3
['/var/pretix',  '/var/pretix',  '/var/pretix/venv/bin',  '/usr/local/lib/python37.zip',  '/usr/local/lib/python3.7',  '/usr/local/lib/python3.7/lib-dynload',  '/var/pretix/venv/lib/python3.7/site-packages']
Thu, 18 Jul 2019 15:36:14 +0000


@pipermerriam @davesque can you have a look? This one would be quite urgent - but I can't look at it in the next 4h

Potentially unsafe ERC20 transaction verification

What was wrong?

The code here that we use to get token transfer information from ERC20 contract transactions is potentially unsafe. It will fail if an ERC20 token contract does not throw an exception when a sender has insufficient funds for a transfer. In that case, an attacker could make a transfer for the correct amount without having adequate token funds and the resulting transaction will pass our verification checks. We've confirmed that the DAI contract will throw an exception when a sender doesn't have adequate funds so this issue doesn't currently affect our supported payment tokens (which are only DAI).

How can it be fixed?

If a token contract does not raise for insufficient funds, we'll need to check for log events, in payment transactions, that indicate a successful transfer.

Convert wei to ETH in manual ETH payment

I used this checkout to buy Devcon5 tickets and when presented with an option to pay to an Ethereum address, the amount shown was in wei rather than ETH. Most wallets default to ETH, or even only allow sending in ETH denominations, and so I request that the amount shown on the checkout page also be converted from wei to ETH for ease of use.

Deal with user error

this is a meta-issue for an alternative approach to #64
the reason for suggesting #64 was mainly that in a praxis dogfooding test it showed users ignored warnings that where written in bold - it looked like this:
Selection_156
The idea was now to use HD wallets to prevent this user error.
But there might actually be another way.
For people that did this mistake: offer an option to sign a message with their account to prove the tx is from them and correlate this way.
This should solve most of the problems.
The rest (e.g. using an exchange that rounds) can even be small enough that manual support is feasible.
Advantage is that is is less complexity and cheaper.

HD wallet support

means one address per payment in via one HD wallet.

reasons:

  • prevent user error (and we do not need to state these advises that then get ignored)
  • a bit more anonymity
  • event organizer does not directly disclose how many participants (paid via ethereum)
  • prevent attacks like #8

testnet support

it would be great to support a testnet like goerli for testing the plugin without spending fees and spamming the main network

Add logging

We should add somewhat verbose logging statements into the payments module.

Ethplorer transaction data not accurate

It appears that Ethplorer doesn't reliably give the value parameter back when querying their API.

See this transaction:

{"timestamp":1562560887,"from":"0x9bb37d6564500b01135887f94ce86515cfaa5f80","to":"0x0000000000000000000000000000000000000000","hash":"0x50fcadf54f2e7f0473dfd2083ab55ac58fd6cbd6e2a30e7967e9686d28b1fa2d","value":1.0e-24,"input":"...","success":true}`

It shows a value of 1.0e-24

Here's the same tx on etherscan:

https://etherscan.io/tx/0x50fcadf54f2e7f0473dfd2083ab55ac58fd6cbd6e2a30e7967e9686d28b1fa2d

It's not possible to recover an accurate amount from the data returned by ethplorer.

Automatic confirmations

All automatic confirmation code was removed for security reasons in #49 to be able to run the first 2 waves. But as now in wave2 the dust settled and the current system seems to be stable enough so we can add complexity again. Here an outline on how to get automatic confirmations going.
The following plan:

  • plugin queries blockscout and perhaps etherscan for all transactions for the configured incoming address - it needs to do it for 3 transaction types: normal transactions, internal transactions and token transactions
  • then the plugin validates these transactions via e.g. infura RPC
  • then the plugin matches the transactions to orders
  • then the plugin marks the matching orders as paid

This should already remove most of the current manual work. After this we can get #74 and #75 going to care for the orders with user errors.

For better and intense testing I suggest we do #5 before

prove you own the address via 681

via 0ETH tx to an address that is generated from static order-info
needed as 681 itself it is only unidirectional
so a tx that ends up on chain would be the needed "back channel"

Improve payment screen UX

Selection_358

It will already be better after #64 as we do not need these weird sums or mentioning the whole "exactly" thing
But perhaps hiding the QR-Code and refreshing the link with a Web3Connec/WalletConnect logo could be nice

Security: check/fix scenario where address is reused

just skimming the code (as a non python dev) I could imagine the following attack:

  • order ticket 1
  • pay for ticket 1
  • order ticket 2
  • specify the same address used in the payment for ticket 1
  • this might mark ticket 2 as payed but the user only payed for one ticket

support xDAI

it would be great if one could pay via xDAI for better UX and lower fees.

Ethplorer API rate limiting

What is wrong?

We are using a free API key for ethplorer:

https://github.com/esPass/pretix-eth-payment-plugin/blob/eedcd5c562f5afb66cc761a3409f88476fdb9118/pretix_eth/payment.py#L124

The rate limit for free keys is 1 request every 2 seconds:

https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API#freekey-limits

The code which makes this request has no mechanism for detecting the HTTP error that requests will emit resulting in the application throwing a 500 level error.

How can we fix it?

We need to call raise_for_status on the response object before calling json() on it to emit any HTTP based errors in the 4xx and 5xx range:

https://2.python-requests.org//en/latest/api/?highlight=raise_for_status#requests.Response.raise_for_status

Then these need to be caught, and a friendly error pushed back to the user saying something like: "Server too busy, try counting to 10 and re-submitting"

Validate transaction hash

What was wrong?

No validation being done on the transaction hash

How can it be fixed?

Need to validate that the submitted transaction hash is a 0x prefixed hex representation of a 32-byte value.

OperationalError at /devcon/5/order/3N7KD/ivlnbh2kiegzl3bw/pay/change

happened when switching from DAI to ETH payment

Environment:


Request Method: POST
Request URL: http://pretix.staging-8392.devcon.org/devcon/5/order/3N7KD/ivlnbh2kiegzl3bw/pay/change

Django Version: 2.2.2
Python Version: 3.7.3
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'pretix.base',
 'pretix.control',
 'pretix.presale',
 'pretix.multidomain',
 'pretix.api',
 'pretix.helpers',
 'rest_framework',
 'django_filters',
 'compressor',
 'bootstrap3',
 'djangoformsetjs',
 'pretix.plugins.banktransfer',
 'pretix.plugins.stripe',
 'pretix.plugins.paypal',
 'pretix.plugins.ticketoutputpdf',
 'pretix.plugins.sendmail',
 'pretix.plugins.statistics',
 'pretix.plugins.reports',
 'pretix.plugins.checkinlists',
 'pretix.plugins.pretixdroid',
 'pretix.plugins.badges',
 'pretix.plugins.manualpayment',
 'django_markup',
 'django_otp',
 'django_otp.plugins.otp_totp',
 'django_otp.plugins.otp_static',
 'statici18n',
 'django_countries',
 'hijack',
 'compat',
 'oauth2_provider',
 'pretix_eth',
 'pretix_espass',
 'pretix_bitpay']
Installed Middleware:
['pretix.api.middleware.IdempotencyMiddleware',
 'django.middleware.common.CommonMiddleware',
 'pretix.multidomain.middlewares.MultiDomainMiddleware',
 'pretix.multidomain.middlewares.SessionMiddleware',
 'pretix.multidomain.middlewares.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'pretix.control.middleware.PermissionMiddleware',
 'pretix.control.middleware.AuditLogMiddleware',
 'pretix.base.middleware.LocaleMiddleware',
 'pretix.base.middleware.SecurityMiddleware',
 'pretix.presale.middleware.EventMiddleware',
 'pretix.api.middleware.ApiScopeMiddleware']



Traceback:

File "/var/pretix/venv/lib/python3.7/site-packages/redis/connection.py" in send_packed_command
  600.                 self._sock.sendall(item)

During handling of the above exception ([Errno 104] Connection reset by peer), another exception occurred:

File "/var/pretix/venv/lib/python3.7/site-packages/kombu/connection.py" in _reraise_as_library_errors
  431.             yield

File "/var/pretix/venv/lib/python3.7/site-packages/celery/app/base.py" in send_task
  755.                     self.backend.on_task_call(P, task_id)

File "/var/pretix/venv/lib/python3.7/site-packages/celery/backends/redis.py" in on_task_call
  294.             self.result_consumer.consume_from(task_id)

File "/var/pretix/venv/lib/python3.7/site-packages/celery/backends/redis.py" in consume_from
  136.         self._consume_from(task_id)

File "/var/pretix/venv/lib/python3.7/site-packages/celery/backends/redis.py" in _consume_from
  142.             self._pubsub.subscribe(key)

File "/var/pretix/venv/lib/python3.7/site-packages/redis/client.py" in subscribe
  3096.         ret_val = self.execute_command('SUBSCRIBE', *iterkeys(new_channels))

File "/var/pretix/venv/lib/python3.7/site-packages/redis/client.py" in execute_command
  3009.         self._execute(connection, connection.send_command, *args)

File "/var/pretix/venv/lib/python3.7/site-packages/redis/client.py" in _execute
  3013.             return command(*args)

File "/var/pretix/venv/lib/python3.7/site-packages/redis/connection.py" in send_command
  620.         self.send_packed_command(self.pack_command(*args))

File "/var/pretix/venv/lib/python3.7/site-packages/redis/connection.py" in send_packed_command
  613.                                   (errno, errmsg))

During handling of the above exception (Error 104 while writing to socket. Connection reset by peer.), another exception occurred:

File "/var/pretix/venv/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/var/pretix/venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/var/pretix/venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/django/views/generic/base.py" in view
  71.             return self.dispatch(request, *args, **kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapper
  45.         return bound_method(*args, **kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/django/views/decorators/clickjacking.py" in wrapped_view
  50.         resp = view_func(*args, **kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/pretix/presale/views/order.py" in dispatch
  459.         return super().dispatch(request, *args, **kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/pretix/presale/views/robots.py" in dispatch
  7.         resp = super().dispatch(request, *args, **kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/django/views/generic/base.py" in dispatch
  97.         return handler(request, *args, **kwargs)

File "/var/pretix/venv/lib/python3.7/site-packages/pretix/presale/views/order.py" in post
  542.         for p in self.provider_forms:

File "/var/pretix/venv/lib/python3.7/site-packages/django/utils/functional.py" in __get__
  80.         res = instance.__dict__[self.name] = self.func(instance)

File "/var/pretix/venv/lib/python3.7/site-packages/pretix/presale/views/order.py" in provider_forms
  526.                 form = provider.payment_form_render(self.request, abs(pending_sum + fee - current_fee))

File "/var/pretix/venv/lib/python3.7/site-packages/pretix/plugins/stripe/payment.py" in payment_form_render
  576.             stripe_verify_domain.apply_async(args=(self.event.pk, request.host))

File "/var/pretix/venv/lib/python3.7/site-packages/celery/app/task.py" in apply_async
  570.             **options

File "/var/pretix/venv/lib/python3.7/site-packages/celery/app/base.py" in send_task
  756.                 amqp.send_task_message(P, name, message, **options)

File "/usr/local/lib/python3.7/contextlib.py" in __exit__
  130.                 self.gen.throw(type, value, traceback)

File "/var/pretix/venv/lib/python3.7/site-packages/kombu/connection.py" in _reraise_as_library_errors
  436.                     sys.exc_info()[2])

File "/var/pretix/venv/lib/python3.7/site-packages/vine/five.py" in reraise
  194.             raise value.with_traceback(tb)

File "/var/pretix/venv/lib/python3.7/site-packages/kombu/connection.py" in _reraise_as_library_errors
  431.             yield

File "/var/pretix/venv/lib/python3.7/site-packages/celery/app/base.py" in send_task
  755.                     self.backend.on_task_call(P, task_id)

File "/var/pretix/venv/lib/python3.7/site-packages/celery/backends/redis.py" in on_task_call
  294.             self.result_consumer.consume_from(task_id)

File "/var/pretix/venv/lib/python3.7/site-packages/celery/backends/redis.py" in consume_from
  136.         self._consume_from(task_id)

File "/var/pretix/venv/lib/python3.7/site-packages/celery/backends/redis.py" in _consume_from
  142.             self._pubsub.subscribe(key)

File "/var/pretix/venv/lib/python3.7/site-packages/redis/client.py" in subscribe
  3096.         ret_val = self.execute_command('SUBSCRIBE', *iterkeys(new_channels))

File "/var/pretix/venv/lib/python3.7/site-packages/redis/client.py" in execute_command
  3009.         self._execute(connection, connection.send_command, *args)

File "/var/pretix/venv/lib/python3.7/site-packages/redis/client.py" in _execute
  3013.             return command(*args)

File "/var/pretix/venv/lib/python3.7/site-packages/redis/connection.py" in send_command
  620.         self.send_packed_command(self.pack_command(*args))

File "/var/pretix/venv/lib/python3.7/site-packages/redis/connection.py" in send_packed_command
  613.                                   (errno, errmsg))

Exception Type: OperationalError at /devcon/5/order/3N7KD/ivlnbh2kiegzl3bw/pay/change
Exception Value: Error 104 while writing to socket. Connection reset by peer.

Security/Decentralisation: get exchange rate via makerDAO oracle

this

rate = requests.get('https://api.bitfinex.com/v1/pubticker/' + request.session['fm_currency'] + 'usd')

could be replaced with a query of the MakerDAO price oracle - perhaps even via INCUBED.

Otherwise it is easy for bitfinex to get cheap tickets as far as I see ,-)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.