diff --git a/src/avalanche/avalanche.h b/src/avalanche/avalanche.h
--- a/src/avalanche/avalanche.h
+++ b/src/avalanche/avalanche.h
@@ -24,6 +24,11 @@
  */
 static constexpr size_t AVALANCHE_DEFAULT_COOLDOWN = 100;
 
+/**
+ * Is avalanche peer discovery enabled.
+ */
+static constexpr bool AVALANCHE_DEFAULT_SHARE_PROOFS = false;
+
 /**
  * Global avalanche instance.
  */
diff --git a/src/init.cpp b/src/init.cpp
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1223,6 +1223,13 @@
         "-enableavalanche",
         strprintf("Enable avalanche (default: %u)", AVALANCHE_DEFAULT_ENABLED),
         ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
+    argsman.AddArg(
+        "-exchangeavaproofs",
+        strprintf("Exchange avalanche proofs when meeting an avalanche peer."
+                  "If disabled, peers can only be added via the "
+                  "addavalanchenode RPC command (default: %u)",
+                  AVALANCHE_DEFAULT_SHARE_PROOFS),
+        ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
     argsman.AddArg(
         "-avacooldown",
         strprintf("Mandatory cooldown between two avapoll (default: %u)",
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2744,7 +2744,8 @@
 bool IsAvalancheMessageType(const std::string &msg_type) {
     return msg_type == NetMsgType::AVAHELLO ||
            msg_type == NetMsgType::AVAPOLL ||
-           msg_type == NetMsgType::AVARESPONSE;
+           msg_type == NetMsgType::AVARESPONSE ||
+           msg_type == NetMsgType::AVAPROOF;
 }
 
 void PeerManager::ProcessMessage(const Config &config, CNode &pfrom,
@@ -4122,6 +4123,28 @@
                                  GetTime<std::chrono::microseconds>(),
                                  preferred);
         }
+        return;
+    }
+
+    if (msg_type == NetMsgType::AVAPROOF) {
+        if (!gArgs.GetBoolArg("-exchangeavaproofs",
+                              AVALANCHE_DEFAULT_SHARE_PROOFS)) {
+            return;
+        }
+
+        auto proof = std::make_shared<avalanche::Proof>();
+        vRecv >> *proof;
+
+        if (pfrom.m_avalanche_state &&
+            proof->getId() ==
+                pfrom.m_avalanche_state->delegation.getProofId() &&
+            g_avalanche->addNode(pfrom.GetId(), proof,
+                                 pfrom.m_avalanche_state->delegation)) {
+            LogPrint(BCLog::NET, "added avalanche node=%d\n", pfrom.GetId());
+        } else if (g_avalanche->addProof(proof)) {
+            LogPrint(BCLog::NET, "added new proof id=%s\n",
+                     proof->getId().GetHex());
+        }
 
         return;
     }
diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py
--- a/test/functional/abc_p2p_avalanche.py
+++ b/test/functional/abc_p2p_avalanche.py
@@ -22,9 +22,10 @@
     CInv,
     FromHex,
     hash256,
-    msg_avahello,
     msg_avapoll,
+    msg_avahello,
     MSG_AVA_PROOF,
+    msg_avaproof,
     msg_getdata,
     msg_tcpavaresponse,
     NODE_AVALANCHE,
@@ -57,6 +58,7 @@
         self.avahello = None
         self.avaresponses = []
         self.avapolls = []
+        self.avaproof = None
         super().__init__()
 
     def peer_connect(self, *args, **kwargs):
@@ -133,9 +135,26 @@
         msg.hello.delegation = delegation
         msg.hello.sig = delegated_privkey.sign_schnorr(local_sighash)
         self.send_message(msg)
-
         return delegation.proofid
 
+    def on_avaproof(self, message):
+        assert(self.avaproof is None)
+        self.avaproof = message
+
+    def wait_for_avaproof(self, timeout=10):
+        wait_until(
+            lambda: self.avaproof is not None,
+            timeout=timeout,
+            lock=p2p_lock)
+
+        with p2p_lock:
+            return self.avaproof
+
+    def send_avaproof(self, proof_hex):
+        msg_proof = msg_avaproof()
+        msg_proof.proof = FromHex(AvalancheProof(), proof_hex)
+        self.send_message(msg_proof)
+
 
 class AvalancheTest(BitcoinTestFramework):
     def set_test_params(self):
@@ -374,6 +393,7 @@
         self.restart_node(0, self.extra_args[0] + [
             "-avaproof={}".format(proof),
             "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN",
+            "-exchangeavaproofs=1",
         ])
 
         assert_equal(
@@ -404,6 +424,18 @@
             bytes_to_wif(privkey.get_bytes()),
             delegated_key.get_pubkey().get_bytes().hex(),
             None)
+        good_interface.send_avahello(interface_delegation_hex, delegated_key)
+        expected_proofid = FromHex(
+            AvalancheProof(),
+            interface_proof_hex).proofid
+        good_interface.wait_for_getdata([expected_proofid])
+
+        self.log.info("Test that node adds an avalanche peer")
+        good_interface.send_avaproof(interface_proof_hex)
+        wait_until(
+            lambda: len(node.getavalanchepeerinfo()) > 0,
+            timeout=5,
+            lock=p2p_lock)
 
         self.log.info("Test that wrong avahello signature causes a ban")
         bad_interface = get_node()
@@ -443,11 +475,8 @@
             "Proof has been inv'ed recently, check it can be requested")
         good_interface.send_message(getdata)
 
-        def proof_received(peer):
-            with p2p_lock:
-                return peer.last_message.get(
-                    "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid
-        wait_until(lambda: proof_received(good_interface))
+        avaproof = good_interface.wait_for_avaproof()
+        assert_equal(avaproof.proof.serialize().hex(), proof)
 
         # Restart the node
         self.restart_node(0, self.extra_args[0] + [
@@ -464,6 +493,13 @@
         # Give enough time for the node to answer. Since we cannot check for a
         # non-event this is the best we can do
         time.sleep(2)
+
+        def proof_received(peer):
+            with p2p_lock:
+                return peer.last_message.get(
+                    "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid
+
+        wait_until(lambda: proof_received(good_interface))
         assert not proof_received(peer)
 
         self.log.info("The proof is known for long enough to be requested")
@@ -471,7 +507,8 @@
         node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY)
 
         peer.send_message(getdata)
-        wait_until(lambda: proof_received(peer))
+        avaproof = peer.wait_for_avaproof()
+        assert_equal(avaproof.proof.serialize().hex(), proof)
 
 
 if __name__ == '__main__':
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -14,6 +14,7 @@
     MAX_PROTOCOL_MESSAGE_LENGTH,
     msg_avahello,
     msg_avapoll,
+    msg_avaproof,
     msg_avaresponse,
     msg_getdata,
     msg_headers,
@@ -289,6 +290,10 @@
                 ['Misbehaving', '(40 -> 60): unsolicited-avaresponse']):
             msg = msg_avaresponse()
             conn.send_and_ping(msg)
+        with self.nodes[0].assert_debug_log(
+                ['Misbehaving', '(60 -> 80): unsolicited-avaproof']):
+            msg = msg_avaproof()
+            conn.send_and_ping(msg)
         self.nodes[0].disconnect_p2ps()
 
     def test_resource_exhaustion(self):