diff --git a/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java b/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java
index 8fa1501816cb8771763d765995f0ec283fb14421..c3d274660cacd3fe553356b0f0fb42f7c7e7ddf9 100644
--- a/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java
@@ -39,6 +39,8 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.logging.Logger;
 
+import javax.inject.Inject;
+
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
@@ -73,6 +75,7 @@ public class ContactExchangeTaskImpl extends Thread
 	private volatile SecretKey masterSecret;
 	private volatile boolean alice;
 
+	@Inject
 	public ContactExchangeTaskImpl(DatabaseComponent db,
 			AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, Clock clock,
diff --git a/briar-core/src/org/briarproject/contact/ContactModule.java b/briar-core/src/org/briarproject/contact/ContactModule.java
index 2cdf6079eddc4bba0cb54911ce1461d3c3522375..3599eb0ddbfc79cf8d37855b3d18167d79aea945 100644
--- a/briar-core/src/org/briarproject/contact/ContactModule.java
+++ b/briar-core/src/org/briarproject/contact/ContactModule.java
@@ -24,7 +24,8 @@ import dagger.Provides;
 public class ContactModule {
 
 	public static class EagerSingletons {
-		@Inject ContactManager contactManager;
+		@Inject
+		ContactManager contactManager;
 	}
 
 	@Provides
@@ -36,16 +37,8 @@ public class ContactModule {
 	}
 
 	@Provides
-	ContactExchangeTask provideContactExchangeTask(DatabaseComponent db,
-			AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory, Clock clock,
-			ConnectionManager connectionManager, ContactManager contactManager,
-			TransportPropertyManager transportPropertyManager,
-			CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory) {
-		return new ContactExchangeTaskImpl(db, authorFactory, bdfReaderFactory,
-				bdfWriterFactory, clock, connectionManager, contactManager,
-				transportPropertyManager, crypto, streamReaderFactory,
-				streamWriterFactory);
+	ContactExchangeTask provideContactExchangeTask(
+			ContactExchangeTaskImpl contactExchangeTask) {
+		return contactExchangeTask;
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java
index b2947ece4db90335101bc6cb4d1a82424cbaa8a6..73e49a132e70c9a779bb84a308489e85efac96d8 100644
--- a/briar-core/src/org/briarproject/invitation/AliceConnector.java
+++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java
@@ -1,6 +1,6 @@
 package org.briarproject.invitation;
 
-import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.contact.ContactExchangeTask;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
@@ -8,16 +8,9 @@ import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.BdfWriter;
 import org.briarproject.api.data.BdfWriterFactory;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.identity.Author;
-import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.system.Clock;
-import org.briarproject.api.transport.StreamReaderFactory;
-import org.briarproject.api.transport.StreamWriterFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -28,24 +21,20 @@ import java.util.logging.Logger;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-/** A connection thread for the peer being Alice in the invitation protocol. */
+/**
+ * A connection thread for the peer being Alice in the invitation protocol.
+ */
 class AliceConnector extends Connector {
 
 	private static final Logger LOG =
 			Logger.getLogger(AliceConnector.class.getName());
 
-	AliceConnector(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
+	AliceConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
-			StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory,
-			AuthorFactory authorFactory, ConnectionManager connectionManager,
-			ContactManager contactManager, Clock clock, ConnectorGroup group,
+			ContactExchangeTask contactExchangeTask, ConnectorGroup group,
 			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
-		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
-				streamWriterFactory, authorFactory,
-				connectionManager, contactManager, clock, group, plugin,
-				localAuthor, random);
+		super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
+				group, plugin, localAuthor, random);
 	}
 
 	@Override
@@ -119,65 +108,11 @@ class AliceConnector extends Connector {
 			tryToClose(conn, false);
 			return;
 		}
-		// The timestamp is taken after exchanging confirmation results
-		long localTimestamp = clock.currentTimeMillis();
 		// Confirmation succeeded - upgrade to a secure connection
 		if (LOG.isLoggable(INFO))
 			LOG.info(pluginName + " confirmation succeeded");
-		// Derive the header keys
-		SecretKey aliceHeaderKey = crypto.deriveHeaderKey(master, true);
-		SecretKey bobHeaderKey = crypto.deriveHeaderKey(master, false);
-		// Create the readers
-		InputStream streamReader =
-				streamReaderFactory.createInvitationStreamReader(in,
-						bobHeaderKey);
-		r = bdfReaderFactory.createReader(streamReader);
-		// Create the writers
-		OutputStream streamWriter =
-				streamWriterFactory.createInvitationStreamWriter(out,
-						aliceHeaderKey);
-		w = bdfWriterFactory.createWriter(streamWriter);
-		// Derive the invitation nonces
-		byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
-		byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
-		// Exchange pseudonyms, signed nonces, and timestamps
-		Author remoteAuthor;
-		long remoteTimestamp;
-		try {
-			sendPseudonym(w, aliceNonce);
-			sendTimestamp(w, localTimestamp);
-			remoteAuthor = receivePseudonym(r, bobNonce);
-			remoteTimestamp = receiveTimestamp(r);
-			// Close the outgoing stream and expect EOF on the incoming stream
-			w.close();
-			if (!r.eof()) LOG.warning("Unexpected data at end of connection");
-		} catch (GeneralSecurityException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.pseudonymExchangeFailed();
-			tryToClose(conn, true);
-			return;
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.pseudonymExchangeFailed();
-			tryToClose(conn, true);
-			return;
-		}
-		// The agreed timestamp is the minimum of the peers' timestamps
-		long timestamp = Math.min(localTimestamp, remoteTimestamp);
-		// Add the contact
-		try {
-			addContact(remoteAuthor, master, timestamp, true);
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			tryToClose(conn, true);
-			group.pseudonymExchangeFailed();
-			return;
-		}
-		// Reuse the connection as a transport connection
-		reuseConnection(conn);
-		// Pseudonym exchange succeeded
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " pseudonym exchange succeeded");
-		group.pseudonymExchangeSucceeded(remoteAuthor);
+		contactExchangeTask
+				.startExchange(group, localAuthor, master, conn, plugin.getId(),
+						true);
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java
index 1460b953ec1b3ec222ca14fc5e27ebfc93447966..80cca62503af1d3174fe77d846a6771b2f690967 100644
--- a/briar-core/src/org/briarproject/invitation/BobConnector.java
+++ b/briar-core/src/org/briarproject/invitation/BobConnector.java
@@ -1,6 +1,6 @@
 package org.briarproject.invitation;
 
-import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.contact.ContactExchangeTask;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
@@ -8,16 +8,10 @@ import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.BdfWriter;
 import org.briarproject.api.data.BdfWriterFactory;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.identity.Author;
-import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.api.system.Clock;
-import org.briarproject.api.transport.StreamReaderFactory;
-import org.briarproject.api.transport.StreamWriterFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -28,24 +22,20 @@ import java.util.logging.Logger;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-/** A connection thread for the peer being Bob in the invitation protocol. */
+/**
+ * A connection thread for the peer being Bob in the invitation protocol.
+ */
 class BobConnector extends Connector {
 
 	private static final Logger LOG =
 			Logger.getLogger(BobConnector.class.getName());
 
-	BobConnector(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
+	BobConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
-			StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory,
-			AuthorFactory authorFactory, ConnectionManager connectionManager,
-			ContactManager contactManager, Clock clock, ConnectorGroup group,
+			ContactExchangeTask contactExchangeTask, ConnectorGroup group,
 			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
-		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
-				streamWriterFactory, authorFactory,
-				connectionManager, contactManager, clock, group, plugin,
-				localAuthor, random);
+		super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
+				group, plugin, localAuthor, random);
 	}
 
 	@Override
@@ -119,69 +109,11 @@ class BobConnector extends Connector {
 			tryToClose(conn, false);
 			return;
 		}
-		// The timestamp is taken after exchanging confirmation results
-		long localTimestamp = clock.currentTimeMillis();
 		// Confirmation succeeded - upgrade to a secure connection
 		if (LOG.isLoggable(INFO))
 			LOG.info(pluginName + " confirmation succeeded");
-		// Derive the header keys
-		SecretKey aliceHeaderKey = crypto.deriveHeaderKey(master,
-				true);
-		SecretKey bobHeaderKey = crypto.deriveHeaderKey(master,
-				false);
-		// Create the readers
-		InputStream streamReader =
-				streamReaderFactory.createInvitationStreamReader(in,
-						aliceHeaderKey);
-		r = bdfReaderFactory.createReader(streamReader);
-		// Create the writers
-		OutputStream streamWriter =
-				streamWriterFactory.createInvitationStreamWriter(out,
-						bobHeaderKey);
-		w = bdfWriterFactory.createWriter(streamWriter);
-		// Derive the nonces
-		byte[] aliceNonce = crypto.deriveSignatureNonce(master,
-				true);
-		byte[] bobNonce = crypto.deriveSignatureNonce(master,
-				false);
-		// Exchange pseudonyms, signed nonces and timestamps
-		Author remoteAuthor;
-		long remoteTimestamp;
-		try {
-			remoteAuthor = receivePseudonym(r, aliceNonce);
-			remoteTimestamp = receiveTimestamp(r);
-			sendPseudonym(w, bobNonce);
-			sendTimestamp(w, localTimestamp);
-			// Close the outgoing stream and expect EOF on the incoming stream
-			w.close();
-			if (!r.eof()) LOG.warning("Unexpected data at end of connection");
-		} catch (GeneralSecurityException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.pseudonymExchangeFailed();
-			tryToClose(conn, true);
-			return;
-		} catch (IOException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			group.pseudonymExchangeFailed();
-			tryToClose(conn, true);
-			return;
-		}
-		// The agreed timestamp is the minimum of the peers' timestamps
-		long timestamp = Math.min(localTimestamp, remoteTimestamp);
-		// Add the contact
-		try {
-			addContact(remoteAuthor, master, timestamp, false);
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			tryToClose(conn, true);
-			group.pseudonymExchangeFailed();
-			return;
-		}
-		// Reuse the connection as a transport connection
-		reuseConnection(conn);
-		// Pseudonym exchange succeeded
-		if (LOG.isLoggable(INFO))
-			LOG.info(pluginName + " pseudonym exchange succeeded");
-		group.pseudonymExchangeSucceeded(remoteAuthor);
+		contactExchangeTask
+				.startExchange(group, localAuthor, master, conn, plugin.getId(),
+						false);
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java
index 2934a0716e10ae432b7f11b2c6d00a2e50720979..371a635219da4fa83cfab2ed292d9d4222cc3adc 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -2,6 +2,7 @@ package org.briarproject.invitation;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
+import org.briarproject.api.contact.ContactExchangeTask;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
@@ -47,12 +48,7 @@ abstract class Connector extends Thread {
 	protected final CryptoComponent crypto;
 	protected final BdfReaderFactory bdfReaderFactory;
 	protected final BdfWriterFactory bdfWriterFactory;
-	protected final StreamReaderFactory streamReaderFactory;
-	protected final StreamWriterFactory streamWriterFactory;
-	protected final AuthorFactory authorFactory;
-	protected final ConnectionManager connectionManager;
-	protected final ContactManager contactManager;
-	protected final Clock clock;
+	protected final ContactExchangeTask contactExchangeTask;
 	protected final ConnectorGroup group;
 	protected final DuplexPlugin plugin;
 	protected final LocalAuthor localAuthor;
@@ -63,26 +59,15 @@ abstract class Connector extends Thread {
 	private final KeyParser keyParser;
 	private final MessageDigest messageDigest;
 
-	private volatile ContactId contactId = null;
-
-	Connector(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
+	Connector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
-			StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory,
-			AuthorFactory authorFactory, ConnectionManager connectionManager,
-			ContactManager contactManager, Clock clock, ConnectorGroup group,
+			ContactExchangeTask contactExchangeTask, ConnectorGroup group,
 			DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
 		super("Connector");
 		this.crypto = crypto;
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.bdfWriterFactory = bdfWriterFactory;
-		this.streamReaderFactory = streamReaderFactory;
-		this.streamWriterFactory = streamWriterFactory;
-		this.authorFactory = authorFactory;
-		this.connectionManager = connectionManager;
-		this.contactManager = contactManager;
-		this.clock = clock;
+		this.contactExchangeTask = contactExchangeTask;
 		this.group = group;
 		this.plugin = plugin;
 		this.localAuthor = localAuthor;
@@ -159,65 +144,6 @@ abstract class Connector extends Thread {
 		return confirmed;
 	}
 
-	protected void sendPseudonym(BdfWriter w, byte[] nonce)
-			throws GeneralSecurityException, IOException {
-		// Sign the nonce
-		Signature signature = crypto.getSignature();
-		KeyParser keyParser = crypto.getSignatureKeyParser();
-		byte[] privateKey = localAuthor.getPrivateKey();
-		signature.initSign(keyParser.parsePrivateKey(privateKey));
-		signature.update(nonce);
-		byte[] sig = signature.sign();
-		// Write the name, public key and signature
-		w.writeString(localAuthor.getName());
-		w.writeRaw(localAuthor.getPublicKey());
-		w.writeRaw(sig);
-		w.flush();
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent pseudonym");
-	}
-
-	protected Author receivePseudonym(BdfReader r, byte[] nonce)
-			throws GeneralSecurityException, IOException {
-		// Read the name, public key and signature
-		String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
-		byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received pseudonym");
-		// Verify the signature
-		Signature signature = crypto.getSignature();
-		KeyParser keyParser = crypto.getSignatureKeyParser();
-		signature.initVerify(keyParser.parsePublicKey(publicKey));
-		signature.update(nonce);
-		if (!signature.verify(sig)) {
-			if (LOG.isLoggable(INFO))
-				LOG.info(pluginName + " invalid signature");
-			throw new GeneralSecurityException();
-		}
-		return authorFactory.createAuthor(name, publicKey);
-	}
-
-	protected void sendTimestamp(BdfWriter w, long timestamp)
-			throws IOException {
-		w.writeLong(timestamp);
-		w.flush();
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent timestamp");
-	}
-
-	protected long receiveTimestamp(BdfReader r) throws IOException {
-		long timestamp = r.readLong();
-		if (timestamp < 0) throw new FormatException();
-		if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received timestamp");
-		return timestamp;
-	}
-
-	protected ContactId addContact(Author remoteAuthor, SecretKey master,
-			long timestamp, boolean alice) throws DbException {
-		// Add the contact to the database
-		contactId = contactManager.addContact(remoteAuthor,
-				localAuthor.getId(), master, timestamp, alice, true);
-		return contactId;
-	}
-
 	protected void tryToClose(DuplexTransportConnection conn,
 			boolean exception) {
 		try {
@@ -228,10 +154,4 @@ abstract class Connector extends Thread {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
 	}
-
-	protected void reuseConnection(DuplexTransportConnection conn) {
-		if (contactId == null) throw new IllegalStateException();
-		TransportId t = plugin.getId();
-		connectionManager.manageOutgoingConnection(contactId, t, conn);
-	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
index 30447a06bc71a0b0d674604d231e3bc7b60a7c4d..7b2545789e076a070c0d8da8b02528977baf6fe9 100644
--- a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
+++ b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
@@ -1,5 +1,7 @@
 package org.briarproject.invitation;
 
+import org.briarproject.api.contact.ContactExchangeListener;
+import org.briarproject.api.contact.ContactExchangeTask;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
@@ -34,8 +36,11 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
 
-/** A task consisting of one or more parallel connection attempts. */
-class ConnectorGroup extends Thread implements InvitationTask {
+/**
+ * A task consisting of one or more parallel connection attempts.
+ */
+class ConnectorGroup extends Thread implements InvitationTask,
+		ContactExchangeListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(ConnectorGroup.class.getName());
@@ -43,13 +48,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	private final CryptoComponent crypto;
 	private final BdfReaderFactory bdfReaderFactory;
 	private final BdfWriterFactory bdfWriterFactory;
-	private final StreamReaderFactory streamReaderFactory;
-	private final StreamWriterFactory streamWriterFactory;
-	private final AuthorFactory authorFactory;
-	private final ConnectionManager connectionManager;
+	private final ContactExchangeTask contactExchangeTask;
 	private final IdentityManager identityManager;
-	private final ContactManager contactManager;
-	private final Clock clock;
 	private final PluginManager pluginManager;
 	private final AuthorId localAuthorId;
 	private final int localInvitationCode, remoteInvitationCode;
@@ -65,26 +65,18 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	private boolean localMatched = false, remoteMatched = false;
 	private String remoteName = null;
 
-	ConnectorGroup(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
+	ConnectorGroup(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
-			StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory,
-			AuthorFactory authorFactory, ConnectionManager connectionManager,
-			IdentityManager identityManager, ContactManager contactManager,
-			Clock clock, PluginManager pluginManager, AuthorId localAuthorId,
-			int localInvitationCode, int remoteInvitationCode) {
+			ContactExchangeTask contactExchangeTask,
+			IdentityManager identityManager, PluginManager pluginManager,
+			AuthorId localAuthorId, int localInvitationCode,
+			int remoteInvitationCode) {
 		super("ConnectorGroup");
 		this.crypto = crypto;
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.bdfWriterFactory = bdfWriterFactory;
-		this.streamReaderFactory = streamReaderFactory;
-		this.streamWriterFactory = streamWriterFactory;
-		this.authorFactory = authorFactory;
-		this.connectionManager = connectionManager;
+		this.contactExchangeTask = contactExchangeTask;
 		this.identityManager = identityManager;
-		this.contactManager = contactManager;
-		this.clock = clock;
 		this.pluginManager = pluginManager;
 		this.localAuthorId = localAuthorId;
 		this.localInvitationCode = localInvitationCode;
@@ -143,7 +135,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
 				c.start();
 			}
 		} else {
-			for (DuplexPlugin plugin: pluginManager.getInvitationPlugins()) {
+			for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
 				Connector c = createBobConnector(plugin, localAuthor);
 				connectors.add(c);
 				c.start();
@@ -173,9 +165,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
 				remoteInvitationCode);
 		return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
-				streamReaderFactory, streamWriterFactory, authorFactory,
-				connectionManager, contactManager, clock, this, plugin,
-				localAuthor, random);
+				contactExchangeTask, this, plugin, localAuthor, random);
 	}
 
 	private Connector createBobConnector(DuplexPlugin plugin,
@@ -183,9 +173,7 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
 				localInvitationCode);
 		return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
-				streamReaderFactory, streamWriterFactory, authorFactory,
-				connectionManager, contactManager, clock, this, plugin,
-				localAuthor, random);
+				contactExchangeTask, this, plugin, localAuthor, random);
 	}
 
 	public void localConfirmationSucceeded() {
@@ -265,7 +253,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		for (InvitationListener l : listeners) l.remoteConfirmationFailed();
 	}
 
-	void pseudonymExchangeSucceeded(Author remoteAuthor) {
+	@Override
+	public void contactExchangeSucceeded(Author remoteAuthor) {
 		String name = remoteAuthor.getName();
 		lock.lock();
 		try {
@@ -277,7 +266,14 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			l.pseudonymExchangeSucceeded(name);
 	}
 
-	void pseudonymExchangeFailed() {
+	@Override
+	public void duplicateContact(Author remoteAuthor) {
+		// TODO differentiate
+		for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
+	}
+
+	@Override
+	public void contactExchangeFailed() {
 		for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/InvitationModule.java b/briar-core/src/org/briarproject/invitation/InvitationModule.java
index 1a604c9e6b89aebf1d75e69807bdbd859b8aaa11..b4262bca0d863ac5d2d8b93fcadd64cc50fb81d2 100644
--- a/briar-core/src/org/briarproject/invitation/InvitationModule.java
+++ b/briar-core/src/org/briarproject/invitation/InvitationModule.java
@@ -25,17 +25,8 @@ public class InvitationModule {
 
 	@Provides
 	@Singleton
-	InvitationTaskFactory provideInvitationTaskFactory(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory,
-			AuthorFactory authorFactory, ConnectionManager connectionManager,
-			IdentityManager identityManager, ContactManager contactManager,
-			Clock clock, PluginManager pluginManager) {
-		return new InvitationTaskFactoryImpl(crypto, bdfReaderFactory,
-				bdfWriterFactory, streamReaderFactory, streamWriterFactory,
-				authorFactory, connectionManager, identityManager,
-				contactManager, clock, pluginManager);
+	InvitationTaskFactory provideInvitationTaskFactory(
+			InvitationTaskFactoryImpl invitationTaskFactory) {
+		return invitationTaskFactory;
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
index 49874be45da6a720bfd912a13085a868c5aad73a..750153956d63fe692162301caf52291dc5c8875a 100644
--- a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
+++ b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
@@ -1,19 +1,14 @@
 package org.briarproject.invitation;
 
-import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.contact.ContactExchangeTask;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.BdfWriterFactory;
-import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.invitation.InvitationTask;
 import org.briarproject.api.invitation.InvitationTaskFactory;
-import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.PluginManager;
-import org.briarproject.api.system.Clock;
-import org.briarproject.api.transport.StreamReaderFactory;
-import org.briarproject.api.transport.StreamWriterFactory;
 
 import javax.inject.Inject;
 
@@ -22,41 +17,28 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 	private final CryptoComponent crypto;
 	private final BdfReaderFactory bdfReaderFactory;
 	private final BdfWriterFactory bdfWriterFactory;
-	private final StreamReaderFactory streamReaderFactory;
-	private final StreamWriterFactory streamWriterFactory;
-	private final AuthorFactory authorFactory;
-	private final ConnectionManager connectionManager;
+	private final ContactExchangeTask contactExchangeTask;
 	private final IdentityManager identityManager;
-	private final ContactManager contactManager;
-	private final Clock clock;
 	private final PluginManager pluginManager;
 
 	@Inject
 	InvitationTaskFactoryImpl(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory, BdfWriterFactory bdfWriterFactory,
-			StreamReaderFactory streamReaderFactory,
-			StreamWriterFactory streamWriterFactory,
-			AuthorFactory authorFactory, ConnectionManager connectionManager,
-			IdentityManager identityManager, ContactManager contactManager,
-			Clock clock, PluginManager pluginManager) {
+			BdfReaderFactory bdfReaderFactory,
+			BdfWriterFactory bdfWriterFactory,
+			ContactExchangeTask contactExchangeTask,
+			IdentityManager identityManager, PluginManager pluginManager) {
 		this.crypto = crypto;
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.bdfWriterFactory = bdfWriterFactory;
-		this.streamReaderFactory = streamReaderFactory;
-		this.streamWriterFactory = streamWriterFactory;
-		this.authorFactory = authorFactory;
-		this.connectionManager = connectionManager;
+		this.contactExchangeTask = contactExchangeTask;
 		this.identityManager = identityManager;
-		this.contactManager = contactManager;
-		this.clock = clock;
 		this.pluginManager = pluginManager;
 	}
 
 	public InvitationTask createTask(AuthorId localAuthorId, int localCode,
 			int remoteCode) {
 		return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
-				streamReaderFactory, streamWriterFactory, authorFactory,
-				connectionManager, identityManager, contactManager, clock,
-				pluginManager, localAuthorId, localCode, remoteCode);
+				contactExchangeTask, identityManager, pluginManager,
+				localAuthorId, localCode, remoteCode);
 	}
 }