diff --git a/README.md b/README.md
index 5cbb8a7a7b10d181b47603c88a1bfe7c7b4e4d53..0388f0b518a4fd09eb3bb4b3b1cb06c70d87407d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
-Ce programme est une implémentation du jeu Ğeconomicus pour jouer par Internet en client-serveur.
+Ce programme est une implémentation du jeu Ğeconomicus pour jouer par Internet en client-serveur (et éventuellement un peu en P2P).
 Ceci est la partie serveur, en python3.
 
 Un logiciel client sera implémenté aussi, probablement en JavaScript pour être plus facile d'utilisation (pas d'installation pour le joueur).
 Le serveur permettra d'héberger plusieurs parties, afin d'épargner aussi aux animateurs l'installation. Les parties pourront être crées à distance, depuis le client.
+(Si vous voulez m'aider pour la réalisation du client, ça serait avec grand plaisir !)
 
 N'hésitez pas à me contacter si vous avez des idées ou des critiques concernant le principe ou ma manière d'implémenter, de coder... Il faut adapter les règles du jeu au support informatique, ce qui nécessite de faire des choix (par exemple, j'ai décidé de remplacer le système de billets par une valeur numérique, mais je garde le roulement des valeurs).
 
@@ -62,7 +63,8 @@ Les données JSON comprennent un champ "error".
 	54: Game error: that player does not exist
 	55: Game error: wrong player password
 	56: Game error: miss player password
-	57: Game error: miss exchange data
+	57: Game error: miss exchanges data
+	58: Game error: invalid exchange data
 	128: Server error: source not available
 
 Exemple de paquet valide :
@@ -71,9 +73,25 @@ Exemple de paquet valide :
 
 #### Échanges
 
-**(Non implémenté)** Un échange de valeurs et/ou d'argent entre joueurs nécessite l'accord de tous les joueurs devant fournir de la richesse.  
+Un échange de valeurs et/ou d'argent entre joueurs nécessite l'accord de tous les joueurs devant fournir de la richesse.  
 Pour proposer un échange, les joueurs négocient entre eux (en P2P) les conditions. Quand ils sont en accord, ils envoient chacun au serveur les données de l'échange, contenant la liste des transactions. Le serveur valide les transactions dès qu'il a reçu la confirmation de tous les joueurs impliqués (devant donner quelque chose).
 
+	{ "exchanges": {
+		"player_uid": player_uid,
+		"player_psw": player_psw,
+		"exchanges_data": [
+			[
+				[from_player_uid, to_player_uid, amount],
+				...
+			],
+			...
+		]
+	}
+	# (int) amount: monnaie
+	# (iterable) amount: valeurs (liste de taille N_VALUES)
+	# "exchanges_data": liste des échanges.
+	# Un échange est une liste de transactions d'un joueur vers un autre.
+
 ## Sources
 
 http://www.trm.creationmonetaire.info/TheorieRelativedelaMonnaie.pdf  
@@ -100,8 +118,10 @@ Copyright 2018 Pascal Engélibert
 > You should have received a copy of the GNU Affero General Public License
 > along with Pygeconomicus-server.  If not, see <https://www.gnu.org/licenses/>.
 
-La GNU AGPL impose de publier les sources d'une instance publiquement accessible de ce logiciel, si celles-ci sont modifiées. Pour faciliter cela, le serveur pourra générer et fournir une archive de son propre code source au client si celui-ci en fait la requête.
+La GNU AGPL impose de publier les sources d'une instance publiquement accessible de ce logiciel, si celles-ci sont modifiées. Pour faciliter cela, le serveur peut générer et fournir une archive de son propre code source au client si celui-ci en fait la requête.  
+Si vous hébergez une instance modifiée de ce logiciel accessible publiquement, vous devez juste vérifier que tous les fichiers du logiciel soient également accessibles, donc si vous ajoutez des fichier il faut actualiser la liste `SOURCE_FILES` dans `utils.py`. Cela est aussi valable pour les bibliothèques utilisées, qui doivent être open-source. En cas de doute, vous pouvez chercher dans la FAQ de GNU ou me demander.
 
 ## Contact
 
-Contactez-moi via le site de ZettaScript : https://zettascript.org
+Contactez-moi via le site de ZettaScript : https://zettascript.org/contact.php
+Page du projet : https://zettascript.org/projects/geconomicus/
diff --git a/server.py b/server.py
index 939c2fd319c62d46f8121f651cfc937022da31d5..eb50c592c88eba4990fb6f056411002fe344f0a6 100644
--- a/server.py
+++ b/server.py
@@ -20,9 +20,6 @@
 import money_free, money_debt
 from utils import *
 
-games = []
-games_index = {}
-
 class Player():
 	def __init__(self, name, address, psw, uid):
 		# Consts
@@ -167,6 +164,7 @@ Options:\n\
 	loop = True
 	server_address = (HOST, PORT)
 	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 	sock.bind(server_address)
 	sock.listen(1)
 	logPrint("Server started at "+str(server_address), LOG_INFO)
@@ -270,7 +268,7 @@ Options:\n\
 						g_name = p_data["game_name"]
 					
 						if not g_name in games_index:
-							sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+							sendErrorToClient(client, ERROR_GAME_GAMENOEXIST, resp_lng)# Game error: there is no game with this name
 							continue
 						game = games_index[g_name]
 						
@@ -332,7 +330,7 @@ Options:\n\
 					g_name = p_data["game_name"]
 					
 					if not g_name in games_index:
-						sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+						sendErrorToClient(client, ERROR_GAME_GAMENOEXIST, resp_lng)# Game error: there is no game with this name
 						continue
 					game = games_index[g_name]
 					
@@ -370,7 +368,7 @@ Options:\n\
 					g_name = p_data["game_name"]
 					
 					if not g_name in games_index:
-						sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+						sendErrorToClient(client, ERROR_GAME_GAMENOEXIST, resp_lng)# Game error: there is no game with this name
 						continue
 					game = games_index[g_name]
 					
@@ -387,6 +385,26 @@ Options:\n\
 					
 					resp["rm_game_done"] = True
 				
+				if "new_turn" in p_data:
+					logPrint("Received new turn command", LOG_TRACE)
+					p_resp = True
+					
+					try:
+						g_name = getGameName(p_data, client, resp_lng)
+						game = getGame(g_name, client, resp_lng)
+					except ContinueError:
+						continue
+					
+					g_admin_psw = ""
+					if "admin_psw" in p_data:
+						g_admin_psw = p_data["admin_psw"]
+					if g_admin_psw != game.admin_psw:
+						sendErrorToClient(client, ERROR_GAME_WRONGADMINPSW, resp_lng)# Game error: wrong admin password
+						continue
+					
+					game.newTurn()
+					resp["new_turn_done"] = {"turn":game.turn}
+				
 				if "new_player" in p_data:
 					logPrint("Received new player command", LOG_TRACE)
 					p_resp = True
@@ -397,7 +415,7 @@ Options:\n\
 					g_name = p_data["game_name"]
 					
 					if not g_name in games_index:
-						sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+						sendErrorToClient(client, ERROR_GAME_GAMENOEXIST, resp_lng)# Game error: there is no game with this name
 						continue
 					game = games_index[g_name]
 					
@@ -431,7 +449,7 @@ Options:\n\
 					g_name = p_data["game_name"]
 					
 					if not g_name in games_index:
-						sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+						sendErrorToClient(client, ERROR_GAME_GAMENOEXIST, resp_lng)# Game error: there is no game with this name
 						continue
 					game = games_index[g_name]
 					
@@ -465,7 +483,7 @@ Options:\n\
 					
 					player = None
 				
-				if "exchange" in p_data:
+				if "exchanges" in p_data:
 					logPrint("Received exchange command", LOG_TRACE)
 					p_resp = True
 					
@@ -475,11 +493,11 @@ Options:\n\
 					g_name = p_data["game_name"]
 					
 					if not g_name in games_index:
-						sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+						sendErrorToClient(client, ERROR_GAME_GAMENOEXIST, resp_lng)# Game error: there is no game with this name
 						continue
 					game = games_index[g_name]
 					
-					g = p_data["exchange"]
+					g = p_data["exchanges"]
 					
 					g_player_uid = None
 					if "player_uid" in g:
@@ -498,11 +516,32 @@ Options:\n\
 						sendErrorToClient(client, ERROR_GAME_MISSPLAYERPSW, resp_lng)# Game error: miss player password
 						continue
 					
-					if not "exchange_data" in g:
-						sendErrorToClient(client, ERROR_GAME_MISSEXCHDATA, resp_lng)# Game error: miss exchange data
+					if not "exchanges_data" in g:
+						sendErrorToClient(client, ERROR_GAME_MISSEXCHDATA, resp_lng)# Game error: miss exchanges data
+						continue
+					
+					try:
+						for exch in g["exchanges_data"]:
+							
+							# Check each transaction
+							for exchterm in exch:
+								assert len(exchterm) == 3
+								if not exchterm[0] in game.players_index or not exchterm[1] in game.players_index:
+									sendErrorToClient(client, ERROR_GAME_PLAYERNOEXIST, resp_lng)# Game error: that player does not exist
+									raise ContinueError
+								if type(exchterm[2]) == int or type(exchterm[2]) == float:
+									assert exchterm[2] >= 0
+								else:
+									assert len(exchterm[2]) == game.C["N_VALUES"]
+							
+							# TODO: add to exchanges list, confirm, apply
+					
+					except ContinueError:
+						continue
+					except (TypeError, AssertionError):
+						sendErrorToClient(client, ERROR_GAME_INVEXCHDATA, resp_lng)# Game error: invalid exchange data
 						continue
 					
-					# TODO: check data, add to exchanges list, confirm, apply
 			
 			else:
 				sendErrorToClient(client, ERROR_COM_LANGUAGE, resp_lng)# Communication error: unknown language
diff --git a/testclient.py b/testclient.py
index 0387ec803ed000442207bf614904b98a30b60f3c..f05d243a85b25ab2ec9aeeeebc6c243ddad0cc79 100644
--- a/testclient.py
+++ b/testclient.py
@@ -1,5 +1,3 @@
-import socket, time, json
-from sys import argv
 from utils import *
 
 PROTOCOL_VERSION = b"geco0"
@@ -31,14 +29,14 @@ CONSTS2 = {
 	"START_VALUES": 5 # number of random values for beginning
 }
 
-HOST = getargv(argv, "-h", "127.0.0.1")
+HOST = getargv(sys.argv, "-h", "127.0.0.1")
 try:
-	PORT = int(getargv(argv, "-p", "8651"))
+	PORT = int(getargv(sys.argv, "-p", "8651"))
 except ValueError:
 	print("Error: port must be an integer")
 	exit(1)
 
-if "--help" in argv:
+if "--help" in sys.argv:
 	print("\n\
 PyĞeconomicus test client\n\
 \n\
@@ -68,6 +66,7 @@ def sdata(data, response_intended, lng="json"):
 	raw = json.dumps(data).encode()
 	msg = PROTOCOL_VERSION+b"/"+PROTOCOL_LANG+b"/"+hex(len(raw))[2:].encode()+b"\n"+raw
 	sock.sendall(msg)
+	
 	resp = None
 	if response_intended:
 		paquet = b""
@@ -171,6 +170,10 @@ def removeplayer(name="Test game", player_uid=1, player_psw="", admin_psw=None):
 	else:
 		return sdata({"game_name":name, "rm_player":{"player_uid":player_uid, "player_psw":player_psw}}, True)
 
+def exchanges(data, name="Test game", player_uid=1, player_psw=""):
+	"""Send exchanges"""
+	return sdata({"game_name":name, "exchanges":{"player_uid":player_uid, "player_psw":player_psw, "exchanges_data":data}}, True)
+
 def getsource(outfile="serversourcecode.tar.gz"):
 	"""Request for source code"""
 	f = open(outfile, "wb")
diff --git a/utils.py b/utils.py
index 536ecc9d43e3753f281ad33e6bba5f9a413524db..6bf254be44b2b87fa1943d132990a36afea2fafc 100644
--- a/utils.py
+++ b/utils.py
@@ -20,21 +20,22 @@
 import configparser, socket, json, os, time, random, sys
 
 ERROR_OK = 0
-ERROR_COM_HEADER = 16
-ERROR_COM_PROTOCOL = 17
-ERROR_COM_LANGUAGE = 18
-ERROR_COM_JSON = 19
-ERROR_GAME_USEDGAMENAME = 48
-ERROR_GAME_MISSNEWGAMENAME = 49
-ERROR_GAME_MISSGAMENAME = 50
-ERROR_GAME_NONAMED = 51
-ERROR_GAME_MISSADMINPSW = 52
-ERROR_GAME_WRONGADMINPSW = 53
-ERROR_GAME_PLAYERNOEXIST = 54
-ERROR_GAME_WRONGPLAYERPSW = 55
-ERROR_GAME_MISSPLAYERPSW = 56
-ERROR_GAME_MISSEXCHDATA = 57
-ERROR_SRV_NOSOURCE = 128
+ERROR_COM_HEADER = 16 # Communication error: decoding header
+ERROR_COM_PROTOCOL = 17 # Communication error: unsupported protocol version
+ERROR_COM_LANGUAGE = 18 # Communication error: unknown language
+ERROR_COM_JSON = 19 # Communication error: decoding JSON
+ERROR_GAME_USEDGAMENAME = 48 # Game error: that game name is already used
+ERROR_GAME_MISSNEWGAMENAME = 49 # Game error: miss new game name
+ERROR_GAME_MISSGAMENAME = 50 # Game error: miss game name
+ERROR_GAME_GAMENOEXIST = 51 # Game error: there is no game with this name
+ERROR_GAME_MISSADMINPSW = 52 # Game error: miss admin password
+ERROR_GAME_WRONGADMINPSW = 53 # Game error: wrong admin password
+ERROR_GAME_PLAYERNOEXIST = 54 # Game error: that player does not exist
+ERROR_GAME_WRONGPLAYERPSW = 55 # Game error: wrong player password
+ERROR_GAME_MISSPLAYERPSW = 56 # Game error: miss player password
+ERROR_GAME_MISSEXCHDATA = 57 # Game error: miss exchanges data
+ERROR_GAME_INVEXCHDATA = 58 # Game error: invalid exchange data
+ERROR_SRV_NOSOURCE = 128 # Server error: source not available
 
 LOG_INFO = 1
 LOG_TRACE = 2
@@ -65,6 +66,13 @@ PORT = 8651 # server port
 SOURCE_FILES = ["server.py", "money_free.py", "money_debt.py", "utils.py", "README.md", "LICENSE"]
 SOURCE_ARCHIVE = "sourcecode.tar.gz"
 
+games = []
+games_index = {}
+
+class ContinueError(Exception):
+	def __init__(self):
+		pass
+
 # Server functions
 
 def getargv(argv, arg, default=""):
@@ -108,6 +116,18 @@ def tarSource():
 	tar.close()
 	logPrint("Source archive created at '"+SOURCE_ARCHIVE+"'", LOG_INFO)
 
+def getGameName(p_data, client, resp_lng):
+	if not "game_name" in p_data:
+		sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
+		raise ContinueError
+	return p_data["game_name"]
+
+def getGame(g_name, client, resp_lng):
+	if not g_name in games_index:
+		sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
+		raise ContinueError
+	return games_index[g_name]
+
 # Money functions
 
 def moneySupply(players):