diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/GachaPointRewardDetailEntry.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/GachaPointRewardDetailEntry.cs
new file mode 100644
index 0000000..ec6b7e1
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/GachaPointRewardDetailEntry.cs
@@ -0,0 +1,26 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos;
+
+///
+/// One entry inside gacha_point_rewards[i].reward_list. Different shape from the
+/// post-state-totals used by /pack/open: this is a catalog
+/// declaration ("here's what you'd get if you exchanged"), not a viewer-state assignment.
+/// Wire keys verified against prod capture data_dumps/traffic_prod_tradeables_capture.ndjson.
+///
+[MessagePackObject]
+public class GachaPointRewardDetailEntry
+{
+ [JsonPropertyName("reward_type")]
+ [Key("reward_type")]
+ public int RewardType { get; set; }
+
+ [JsonPropertyName("reward_detail_id")]
+ [Key("reward_detail_id")]
+ public long RewardDetailId { get; set; }
+
+ [JsonPropertyName("reward_number")]
+ [Key("reward_number")]
+ public int RewardNumber { get; set; } = 1;
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/GachaPointRewardDto.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/GachaPointRewardDto.cs
new file mode 100644
index 0000000..fa9ef33
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/GachaPointRewardDto.cs
@@ -0,0 +1,33 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos;
+
+///
+/// One row of gacha_point_rewards[]. The client groups by
+/// (CardBasePrm.ClanType, stringified on the wire) inside GachaPointExchangeInfoTask.Parse.
+///
+[MessagePackObject]
+public class GachaPointRewardDto
+{
+ /// Stringified on the wire (e.g. "0", "1"). CardBasePrm.ClanType value.
+ [JsonPropertyName("class_id")]
+ [Key("class_id")]
+ public string ClassId { get; set; } = "0";
+
+ [JsonPropertyName("card_id")]
+ [Key("card_id")]
+ public long CardId { get; set; }
+
+ [JsonPropertyName("reward_list")]
+ [Key("reward_list")]
+ public List RewardList { get; set; } = new();
+
+ [JsonPropertyName("is_received")]
+ [Key("is_received")]
+ public bool IsReceived { get; set; }
+
+ [JsonPropertyName("is_display_prize")]
+ [Key("is_display_prize")]
+ public bool IsDisplayPrize { get; set; }
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Pack/ExchangeGachaPointRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Pack/ExchangeGachaPointRequest.cs
new file mode 100644
index 0000000..f554b84
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Pack/ExchangeGachaPointRequest.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Pack;
+
+[MessagePackObject]
+public class ExchangeGachaPointRequest : BaseRequest
+{
+ [JsonPropertyName("card_id")]
+ [Key("card_id")]
+ public long CardId { get; set; }
+
+ [JsonPropertyName("parent_gacha_id")]
+ [Key("parent_gacha_id")]
+ public int ParentGachaId { get; set; }
+
+ [JsonPropertyName("odds_gacha_id")]
+ [Key("odds_gacha_id")]
+ public int OddsGachaId { get; set; }
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Pack/GetGachaPointRewardsRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Pack/GetGachaPointRewardsRequest.cs
new file mode 100644
index 0000000..47bb4b9
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Pack/GetGachaPointRewardsRequest.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Pack;
+
+///
+/// Inbound /pack/get_gacha_point_rewards body. Capture shows odds_gacha_id and parent_gacha_id
+/// are always the same value (the pack id); we only consume parent_gacha_id for the lookup.
+///
+[MessagePackObject]
+public class GetGachaPointRewardsRequest : BaseRequest
+{
+ [JsonPropertyName("odds_gacha_id")]
+ [Key("odds_gacha_id")]
+ public int OddsGachaId { get; set; }
+
+ [JsonPropertyName("parent_gacha_id")]
+ [Key("parent_gacha_id")]
+ public int ParentGachaId { get; set; }
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Pack/ExchangeGachaPointResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Pack/ExchangeGachaPointResponse.cs
new file mode 100644
index 0000000..7457db6
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Pack/ExchangeGachaPointResponse.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Pack;
+
+[MessagePackObject]
+public class ExchangeGachaPointResponse
+{
+ ///
+ /// POST-STATE TOTALS dispatched through PlayerStaticData.UpdateHaveUserGoodsNumByJsonData
+ /// on the client (see project_wire_reward_list_post_state memory). The granted card,
+ /// any cascading cosmetics, and the updated gacha-point balance entry all appear here.
+ ///
+ [JsonPropertyName("reward_list")]
+ [Key("reward_list")]
+ public List RewardList { get; set; } = new();
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Pack/GetGachaPointRewardsResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Pack/GetGachaPointRewardsResponse.cs
new file mode 100644
index 0000000..126da19
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Pack/GetGachaPointRewardsResponse.cs
@@ -0,0 +1,12 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Pack;
+
+[MessagePackObject]
+public class GetGachaPointRewardsResponse
+{
+ [JsonPropertyName("gacha_point_rewards")]
+ [Key("gacha_point_rewards")]
+ public List GachaPointRewards { get; set; } = new();
+}