diff --git a/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs b/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs
index b92be10..d15f9b6 100644
--- a/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs
+++ b/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Services.Inventory;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Gift;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Gift;
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Common/PresentDto.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Common/PresentDto.cs
new file mode 100644
index 0000000..cb951eb
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Common/PresentDto.cs
@@ -0,0 +1,55 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Common;
+
+///
+/// Prod sends most numeric-looking fields as STRINGS on the gift endpoints (present_id,
+/// reward_type, reward_detail_id, reward_count, condition_number, present_limit_type).
+/// item_type is an INT. We mirror the prod shape exactly. See the capture at
+/// data_dumps/captures/traffic_event_crate_free_pack.ndjson, /gift/top response (line 18).
+///
+[MessagePackObject]
+public class PresentDto
+{
+ [JsonPropertyName("present_id")]
+ [Key("present_id")]
+ public string PresentId { get; set; } = string.Empty;
+
+ [JsonPropertyName("reward_type")]
+ [Key("reward_type")]
+ public string RewardType { get; set; } = string.Empty;
+
+ [JsonPropertyName("reward_detail_id")]
+ [Key("reward_detail_id")]
+ public string RewardDetailId { get; set; } = string.Empty;
+
+ [JsonPropertyName("reward_count")]
+ [Key("reward_count")]
+ public string RewardCount { get; set; } = string.Empty;
+
+ [JsonPropertyName("condition_number")]
+ [Key("condition_number")]
+ public string ConditionNumber { get; set; } = "0";
+
+ [JsonPropertyName("present_limit_type")]
+ [Key("present_limit_type")]
+ public string PresentLimitType { get; set; } = "0";
+
+ [JsonPropertyName("reward_limit_time")]
+ [Key("reward_limit_time")]
+ public int RewardLimitTime { get; set; }
+
+ [JsonPropertyName("create_time")]
+ [Key("create_time")]
+ public string CreateTime { get; set; } = string.Empty;
+
+ /// Only present on item/pack-ticket entries (reward_type=4); omit on currency entries.
+ [JsonPropertyName("item_type")]
+ [Key("item_type")]
+ public int? ItemType { get; set; }
+
+ [JsonPropertyName("message")]
+ [Key("message")]
+ public string Message { get; set; } = string.Empty;
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftReceiveResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftReceiveResponse.cs
index f7ac3f1..e909391 100644
--- a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftReceiveResponse.cs
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftReceiveResponse.cs
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using MessagePack;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Gift;
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftTopResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftTopResponse.cs
index d6d0176..928816d 100644
--- a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftTopResponse.cs
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Gift/GiftTopResponse.cs
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using MessagePack;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Gift;
@@ -18,53 +19,3 @@ public class GiftTopResponse
[Key("limit_over_present_list")]
public List LimitOverPresentList { get; set; } = new();
}
-
-///
-/// Prod sends most numeric-looking fields as STRINGS on this endpoint
-/// (present_id, reward_type, reward_detail_id, reward_count, condition_number,
-/// present_limit_type). item_type is an INT. We mirror the prod shape exactly.
-///
-[MessagePackObject]
-public class PresentDto
-{
- [JsonPropertyName("present_id")]
- [Key("present_id")]
- public string PresentId { get; set; } = string.Empty;
-
- [JsonPropertyName("reward_type")]
- [Key("reward_type")]
- public string RewardType { get; set; } = string.Empty;
-
- [JsonPropertyName("reward_detail_id")]
- [Key("reward_detail_id")]
- public string RewardDetailId { get; set; } = string.Empty;
-
- [JsonPropertyName("reward_count")]
- [Key("reward_count")]
- public string RewardCount { get; set; } = string.Empty;
-
- [JsonPropertyName("condition_number")]
- [Key("condition_number")]
- public string ConditionNumber { get; set; } = "0";
-
- [JsonPropertyName("present_limit_type")]
- [Key("present_limit_type")]
- public string PresentLimitType { get; set; } = "0";
-
- [JsonPropertyName("reward_limit_time")]
- [Key("reward_limit_time")]
- public int RewardLimitTime { get; set; }
-
- [JsonPropertyName("create_time")]
- [Key("create_time")]
- public string CreateTime { get; set; } = string.Empty;
-
- /// Only present on item/pack-ticket entries (gifts where reward_type=4); omit on currency entries.
- [JsonPropertyName("item_type")]
- [Key("item_type")]
- public int? ItemType { get; set; }
-
- [JsonPropertyName("message")]
- [Key("message")]
- public string Message { get; set; } = string.Empty;
-}