diff --git a/Cargo.lock b/Cargo.lock
index c6e1df1..682d18a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1001,7 +1001,6 @@ checksum = "739e9d7726dc32173fed2d69d17eef3c54682169e4e20ff1d0a45dcd37063cef"
 [[package]]
 name = "libpso"
 version = "0.1.0"
-source = "git+http://git.sharnoth.com/jake/libpso#892d2ed220369f0ff7b7530fa734e722c2b21c2c"
 dependencies = [
  "chrono",
  "psopacket",
@@ -1401,7 +1400,6 @@ dependencies = [
 [[package]]
 name = "psopacket"
 version = "1.0.0"
-source = "git+http://git.sharnoth.com/jake/libpso#892d2ed220369f0ff7b7530fa734e722c2b21c2c"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 582634f..8f6b805 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Jake Probst <jake.probst@gmail.com>"]
 edition = "2018"
 
 [dependencies]
-libpso = { git = "http://git.sharnoth.com/jake/libpso" }
+libpso = { git = "http://git.sharnoth.com/jake/libpso", branch = "sendgc" }
 async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
 futures = "0.3.5"
 rand = "0.7.3"
diff --git a/src/entity/account.rs b/src/entity/account.rs
index 3958412..8247352 100644
--- a/src/entity/account.rs
+++ b/src/entity/account.rs
@@ -1,6 +1,7 @@
 use serde::{Serialize, Deserialize};
 use libpso::character::settings;
 use libpso::character::guildcard;
+use libpso::packet::ship::{GuildcardAccept};
 
 pub const USERFLAG_NEWCHAR: u32      = 0x00000001;
 pub const USERFLAG_DRESSINGROOM: u32 = 0x00000002;
@@ -9,9 +10,13 @@ pub const USERFLAG_DRESSINGROOM: u32 = 0x00000002;
 pub struct UserAccountId(pub u32);
 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub struct UserSettingsId(pub u32);
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct GuildCardDataId(pub u32);
 
+#[derive(Debug)]
+pub enum GuildcardError {
+    GuildcardAlreadyFriend(UserAccountId),
+    GuildcardAlreadyBlocked(UserAccountId),
+    GuildcardListFull,
+}
 
 #[derive(Clone, Debug)]
 pub struct NewUserAccountEntity {
@@ -124,20 +129,26 @@ impl NewGuildCardDataEntity {
     }
 }
 
-// TODO: implement this properly
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct GuildCardDataEntity {
-    pub id: GuildCardDataId,
     pub user_id: UserAccountId,
-    pub guildcard: guildcard::GuildCardData,
+    pub guildcard_data: Box<guildcard::GuildCardData>,
 }
 
 impl GuildCardDataEntity {
     pub fn new(user_id: UserAccountId) -> GuildCardDataEntity {
         GuildCardDataEntity {
-            id: GuildCardDataId(0),
             user_id,
-            guildcard: guildcard::GuildCardData::default(),
+            guildcard_data: Box::new(guildcard::GuildCardData::default()),
         }
     }
+
+    pub fn add_friend(&mut self, new_friend: &GuildcardAccept) -> Result<(), GuildcardError> {
+        let next_open_spot = self.guildcard_data.friends
+                                                        .iter()
+                                                        .position(|&g| g.id == 0)
+                                                        .ok_or(GuildcardError::GuildcardListFull)?;
+        self.guildcard_data.friends[next_open_spot] = guildcard::GuildCard::from(new_friend);
+        Ok(()) // TODO: implement a real error
+    }
 }
diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs
index fd8b1aa..cd4d699 100644
--- a/src/entity/gateway/entitygateway.rs
+++ b/src/entity/gateway/entitygateway.rs
@@ -61,6 +61,10 @@ pub trait EntityGateway: Send + Sync + Clone {
         unimplemented!();
     }
 
+    async fn set_guild_card(&mut self, _id: UserAccountId, _gc_data: GuildCardDataEntity) -> Result<(), GatewayError> {
+        unimplemented!();
+    }
+
     async fn create_item(&mut self, _item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
         unimplemented!();
     }
diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs
index 3d37761..0af09bb 100644
--- a/src/entity/gateway/inmemory.rs
+++ b/src/entity/gateway/inmemory.rs
@@ -21,6 +21,7 @@ pub struct InMemoryGateway {
     equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
     mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
     weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
+    guildcard_entities: Arc<Mutex<BTreeMap<UserAccountId, GuildCardDataEntity>>>,
 }
 
 impl Default for InMemoryGateway {
@@ -37,6 +38,7 @@ impl Default for InMemoryGateway {
             equips: Arc::new(Mutex::new(BTreeMap::new())),
             mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
             weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
+            guildcard_entities: Arc::new(Mutex::new(BTreeMap::new())),
         }
     }
 }
@@ -101,6 +103,7 @@ impl InMemoryGateway {
 impl EntityGateway for InMemoryGateway {
     async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
         let mut users = self.users.lock().unwrap();
+        let mut guildcards = self.guildcard_entities.lock().unwrap();
         let id = users
             .iter()
             .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
@@ -109,7 +112,7 @@ impl EntityGateway for InMemoryGateway {
             id: UserAccountId(id),
             username: user.username,
             password: user.password,
-            guildcard: user.guildcard,
+            guildcard: id,
             team_id: user.team_id,
             banned_until: user.banned_until,
             muted_until: user.muted_until,
@@ -120,7 +123,11 @@ impl EntityGateway for InMemoryGateway {
             at_character: false,
             at_ship: false,
         };
+
+        let guildcard = GuildCardDataEntity::new(UserAccountId(id)); // TODO: NewGuildcardDataEntity ?
         users.insert(user.id, user.clone());
+        guildcards.insert(user.id, guildcard);
+
         Ok(user)
     }
 
@@ -213,8 +220,14 @@ impl EntityGateway for InMemoryGateway {
         Ok(())
     }
 
+    // TODO: ok_or a real error ?
     async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
-        Ok(GuildCardDataEntity::new(user.id))
+        let guildcards = self.guildcard_entities.lock().unwrap();
+        guildcards
+            .iter()
+            .find(|(_, g)| g.user_id == user.id)
+            .map(|(_, g)| g.clone())
+            .ok_or(GatewayError::Error)
     }
 
     async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
@@ -347,4 +360,10 @@ impl EntityGateway for InMemoryGateway {
             Err(GatewayError::Error)
         }
     }
+
+    async fn set_guild_card(&mut self, id: UserAccountId, gc_data: GuildCardDataEntity) -> Result<(), GatewayError> {
+        let mut guildcard = self.guildcard_entities.lock().unwrap();
+        guildcard.insert(id, gc_data);
+        Ok(())
+    }
 }
diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs
index bbf116a..516088c 100644
--- a/src/entity/gateway/postgres/postgres.rs
+++ b/src/entity/gateway/postgres/postgres.rs
@@ -278,9 +278,8 @@ impl EntityGateway for PostgresGateway {
 
     async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
         Ok(GuildCardDataEntity {
-            id: GuildCardDataId(0),
             user_id: user.id,
-            guildcard: guildcard::GuildCardData::default(),
+            guildcard_data: Box::new(guildcard::GuildCardData::default()),
         })
     }
 
diff --git a/src/login/character.rs b/src/login/character.rs
index 69d43e7..610318c 100644
--- a/src/login/character.rs
+++ b/src/login/character.rs
@@ -441,12 +441,10 @@ impl<EG: EntityGateway> CharacterServerState<EG> {
     async fn guildcard_data_header(&mut self, id: ClientId) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
         let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
         let guildcard_data = self.entity_gateway.get_guild_card_data_by_user(client.user.as_ref().unwrap()).await.map_err(|_| CharacterError::CouldNotLoadGuildcard)?;
-
-        let bytes = guildcard_data.guildcard.as_bytes();
+        let bytes = guildcard_data.guildcard_data.as_bytes();
         let mut crc = crc32::Digest::new(crc32::IEEE);
         crc.write(&bytes[..]);
         client.guildcard_data_buffer = Some(bytes.to_vec());
-
         Ok(vec![SendCharacterPacket::GuildcardDataHeader(GuildcardDataHeader::new(bytes.len(), crc.sum32()))])
     }
 
diff --git a/src/ship/packet/handler/communication.rs b/src/ship/packet/handler/communication.rs
index 087913b..13aa276 100644
--- a/src/ship/packet/handler/communication.rs
+++ b/src/ship/packet/handler/communication.rs
@@ -45,3 +45,17 @@ pub async fn write_infoboard<EG: EntityGateway>(id: ClientId,
     entity_gateway.save_character(&client.character).await.unwrap();
     Box::new(None.into_iter())
 }
+
+// TODO: return Result<Box<...>>  so ship can do await? and catch errors?
+pub async fn accept_guildcard<EG: EntityGateway>(id: ClientId,
+                                                accepted_card: &GuildcardAccept,
+                                                clients: &mut Clients,
+                                                entity_gateway: &mut EG)
+                                                -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
+    let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id)).unwrap();
+
+    let mut gc_data = entity_gateway.get_guild_card_data_by_user(&client.user).await.unwrap();
+    gc_data.add_friend(accepted_card).unwrap();
+    entity_gateway.set_guild_card(client.user.id, gc_data).await.unwrap();
+    Box::new(None.into_iter()) // TODO: does the server need to return anything to any client? everything seems to work fine like this...
+}
\ No newline at end of file
diff --git a/src/ship/ship.rs b/src/ship/ship.rs
index ae0fd08..5baf3c4 100644
--- a/src/ship/ship.rs
+++ b/src/ship/ship.rs
@@ -114,6 +114,7 @@ pub enum RecvShipPacket {
     RequestShipBlockList(RequestShipBlockList),
     ItemsToTrade(ItemsToTrade),
     TradeConfirmed(TradeConfirmed),
+    GuildcardAccept(GuildcardAccept),
 }
 
 impl RecvServerPacket for RecvShipPacket {
@@ -155,6 +156,7 @@ impl RecvServerPacket for RecvShipPacket {
             0xD2 => Ok(RecvShipPacket::TradeConfirmed(TradeConfirmed::from_bytes(data)?)),
             0xE7 => Ok(RecvShipPacket::FullCharacterData(Box::new(FullCharacterData::from_bytes(data)?))),
             0x1ED => Ok(RecvShipPacket::SaveOptions(SaveOptions::from_bytes(data)?)),
+            0x4E8 => Ok(RecvShipPacket::GuildcardAccept(GuildcardAccept::from_bytes(data)?)),
             _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
         }
     }
@@ -743,6 +745,9 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
                 let block = self.blocks.with_client(id, &self.clients)?;
                 handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await?
             },
+            RecvShipPacket::GuildcardAccept(guildcard_accept) => {
+                handler::communication::accept_guildcard(id, guildcard_accept, &mut self.clients, &mut self.entity_gateway).await
+            },
         })
     }
 
diff --git a/tests/test_communication.rs b/tests/test_communication.rs
new file mode 100644
index 0000000..2e7e63b
--- /dev/null
+++ b/tests/test_communication.rs
@@ -0,0 +1,55 @@
+use elseware::common::serverstate::{ClientId, ServerState};
+use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
+use elseware::ship::ship::{ShipServerState, RecvShipPacket};
+use libpso::packet::ship::*;
+
+#[path = "common.rs"]
+mod common;
+use common::*;
+
+#[async_std::test]
+async fn test_guildcard_add_friend() {
+    let mut entity_gateway = InMemoryGateway::default();
+    let (user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
+
+    let mut ship = Box::new(ShipServerState::builder()
+        .gateway(entity_gateway.clone())
+        .build());
+
+    log_in_char(&mut ship, ClientId(1), "a1", "a").await;
+    join_lobby(&mut ship, ClientId(1)).await;
+
+    // Accept friend request from "Test Char 2"
+    ship.handle(ClientId(1), &RecvShipPacket::GuildcardAccept(GuildcardAccept {
+        id: 2,
+        name: [84, 101, 115, 116, 32, 67, 104, 97, 114, 32, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+        team: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+        desc: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+        one: 1,
+        language: 0,
+        section_id: 0,
+        class: 0,
+    })).await.unwrap().for_each(drop);
+    
+    let friendlist = entity_gateway.get_guild_card_data_by_user(&user1).await.unwrap();
+
+    assert!(friendlist.guildcard_data.friends[0].name == [84, 101, 115, 116, 32, 67, 104, 97, 114, 32, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+}
+
+
+/*
+TODO: actually write these tests at some point. also add a test for transmute/repr(C)?
+
+#[async_std::test]
+async fn test_guildcard_block_rival() {}
+
+#[async_std::test]
+async fn test_guildcard_write_comment() {}
+
+#[async_std::test]
+async fn test_player_chat() {}
+
+#[async_std::test]
+async fn test_update_infoboard() {}
+
+*/
\ No newline at end of file