#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Env {
Local,
#[default]
Dev,
Production,
}
impl Env {
#[must_use]
pub const fn url(self) -> &'static str {
match self {
Self::Local => "http://localhost:5556",
Self::Dev => "https://grpc.dev.xmtp.network:443",
Self::Production => "https://grpc.production.xmtp.network:443",
}
}
#[must_use]
pub const fn is_secure(self) -> bool {
!matches!(self, Self::Local)
}
}
macro_rules! ffi_enum {
($(#[$meta:meta])* $vis:vis enum $name:ident {
$($(#[$vm:meta])* $variant:ident = $val:expr),* $(,)?
}) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
$vis enum $name { $($(#[$vm])* $variant = $val),* }
impl $name {
#[must_use]
pub const fn from_ffi(v: i32) -> Option<Self> {
match v { $($val => Some(Self::$variant),)* _ => None }
}
}
};
}
ffi_enum! {
pub enum IdentifierKind {
Ethereum = 0,
Passkey = 1,
}
}
ffi_enum! {
pub enum ConversationType {
Dm = 0,
Group = 1,
Sync = 2,
Oneshot = 3,
}
}
ffi_enum! {
pub enum ConsentState {
Unknown = 0,
Allowed = 1,
Denied = 2,
}
}
ffi_enum! {
pub enum ConsentEntityType {
GroupId = 0,
InboxId = 1,
}
}
ffi_enum! {
pub enum MessageKind {
Application = 0,
MembershipChange = 1,
}
}
ffi_enum! {
pub enum DeliveryStatus {
Unpublished = 0,
Published = 1,
Failed = 2,
}
}
ffi_enum! {
pub enum PermissionLevel {
Member = 0,
Admin = 1,
SuperAdmin = 2,
}
}
ffi_enum! {
pub enum GroupPermissionsPreset {
AllMembers = 0,
AdminOnly = 1,
Custom = 2,
}
}
ffi_enum! {
pub enum MembershipState {
Allowed = 0,
Rejected = 1,
Pending = 2,
Restored = 3,
PendingRemove = 4,
}
}
ffi_enum! {
pub enum SortDirection {
Ascending = 0,
Descending = 1,
}
}
ffi_enum! {
pub enum PermissionPolicy {
Allow = 0,
Deny = 1,
AdminOnly = 2,
SuperAdminOnly = 3,
DoesNotExist = 4,
Other = 5,
}
}
impl PermissionPolicy {
#[must_use]
pub const fn to_write_i32(self) -> i32 {
self as i32 + 1
}
}
ffi_enum! {
pub enum PermissionUpdateType {
AddMember = 1,
RemoveMember = 2,
AddAdmin = 3,
RemoveAdmin = 4,
UpdateMetadata = 5,
}
}
ffi_enum! {
pub enum PreferenceKind {
Consent = 0,
HmacKey = 1,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MetadataField {
GroupName,
Description,
ImageUrl,
PinnedFrameUrl,
AppData,
MessageDisappearing,
}
impl MetadataField {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::GroupName => "group_name",
Self::Description => "description",
Self::ImageUrl => "group_image_url_square",
Self::PinnedFrameUrl => "group_pinned_frame_url",
Self::AppData => "app_data",
Self::MessageDisappearing => "message_disappearing",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AccountIdentifier {
pub address: String,
pub kind: IdentifierKind,
}
#[derive(Debug, Clone, Default)]
pub struct CreateGroupOptions {
pub permissions: Option<GroupPermissionsPreset>,
pub name: Option<String>,
pub description: Option<String>,
pub image_url: Option<String>,
pub app_data: Option<String>,
pub disappearing: Option<DisappearingSettings>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CreateDmOptions {
pub disappearing: Option<DisappearingSettings>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ListMessagesOptions {
pub sent_after_ns: i64,
pub sent_before_ns: i64,
pub limit: i64,
pub direction: Option<SortDirection>,
pub delivery_status: Option<DeliveryStatus>,
pub kind: Option<MessageKind>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum ConversationOrderBy {
#[default]
CreatedAt = 0,
LastActivity = 1,
}
impl ConversationOrderBy {
#[must_use]
pub const fn from_ffi(value: i32) -> Option<Self> {
match value {
0 => Some(Self::CreatedAt),
1 => Some(Self::LastActivity),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ListConversationsOptions {
pub conversation_type: Option<ConversationType>,
pub limit: i64,
pub created_after_ns: i64,
pub created_before_ns: i64,
pub last_activity_after_ns: i64,
pub last_activity_before_ns: i64,
pub consent_states: Vec<ConsentState>,
pub order_by: ConversationOrderBy,
pub include_duplicate_dms: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct SendOptions {
pub should_push: bool,
}
impl Default for SendOptions {
fn default() -> Self {
Self { should_push: true }
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DisappearingSettings {
pub from_ns: i64,
pub in_ns: i64,
}
#[derive(Debug, Clone, Copy)]
pub struct PermissionPolicySet {
pub add_member: PermissionPolicy,
pub remove_member: PermissionPolicy,
pub add_admin: PermissionPolicy,
pub remove_admin: PermissionPolicy,
pub update_group_name: PermissionPolicy,
pub update_group_description: PermissionPolicy,
pub update_group_image_url: PermissionPolicy,
pub update_message_disappearing: PermissionPolicy,
pub update_app_data: PermissionPolicy,
}
#[derive(Debug, Clone, Copy)]
pub struct Permissions {
pub preset: GroupPermissionsPreset,
pub policies: PermissionPolicySet,
}
#[derive(Debug, Clone)]
pub struct ConversationMetadata {
pub creator_inbox_id: String,
pub conversation_type: ConversationType,
}
#[derive(Debug, Clone, Copy)]
pub struct Cursor {
pub originator_id: u32,
pub sequence_id: u64,
}
#[derive(Debug, Clone)]
pub struct ConversationDebugInfo {
pub epoch: u64,
pub maybe_forked: bool,
pub fork_details: Option<String>,
pub is_commit_log_forked: Option<bool>,
pub local_commit_log: Option<String>,
pub remote_commit_log: Option<String>,
pub cursors: Vec<Cursor>,
}
#[derive(Debug, Clone)]
pub struct HmacKey {
pub key: Vec<u8>,
pub epoch: i64,
}
#[derive(Debug, Clone)]
pub struct HmacKeyEntry {
pub group_id: String,
pub keys: Vec<HmacKey>,
}
#[derive(Debug, Clone)]
pub struct LastReadTime {
pub inbox_id: String,
pub timestamp_ns: i64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ApiStats {
pub upload_key_package: i64,
pub fetch_key_package: i64,
pub send_group_messages: i64,
pub send_welcome_messages: i64,
pub query_group_messages: i64,
pub query_welcome_messages: i64,
pub subscribe_messages: i64,
pub subscribe_welcomes: i64,
pub publish_commit_log: i64,
pub query_commit_log: i64,
pub get_newest_group_message: i64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct IdentityStats {
pub publish_identity_update: i64,
pub get_identity_updates_v2: i64,
pub get_inbox_ids: i64,
pub verify_smart_contract_wallet_signature: i64,
}
#[derive(Debug, Clone)]
pub struct KeyPackageStatus {
pub installation_id: String,
pub valid: bool,
pub not_before: u64,
pub not_after: u64,
pub validation_error: Option<String>,
}
#[derive(Debug, Clone, Copy)]
pub struct SyncResult {
pub synced: u32,
pub eligible: u32,
}
#[derive(Debug, Clone)]
pub struct InboxState {
pub inbox_id: String,
pub recovery_identifier: String,
pub identifiers: Vec<String>,
pub installation_ids: Vec<String>,
}
pub trait Signer: Send + Sync {
fn identifier(&self) -> AccountIdentifier;
fn sign(&self, text: &str) -> crate::error::Result<Vec<u8>>;
fn is_smart_wallet(&self) -> bool {
false
}
fn chain_id(&self) -> u64 {
1
}
fn block_number(&self) -> u64 {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_url_exact_values() {
assert_eq!(Env::Local.url(), "http://localhost:5556");
assert_eq!(Env::Dev.url(), "https://grpc.dev.xmtp.network:443");
assert_eq!(
Env::Production.url(),
"https://grpc.production.xmtp.network:443"
);
}
#[test]
fn env_local_is_insecure_others_secure() {
assert!(!Env::Local.is_secure());
assert!(Env::Dev.is_secure());
assert!(Env::Production.is_secure());
}
#[test]
fn ffi_discriminants_identity_and_conversation() {
assert_eq!(IdentifierKind::from_ffi(0), Some(IdentifierKind::Ethereum));
assert_eq!(IdentifierKind::from_ffi(1), Some(IdentifierKind::Passkey));
assert_eq!(IdentifierKind::from_ffi(2), None);
assert_eq!(ConversationType::from_ffi(0), Some(ConversationType::Dm));
assert_eq!(ConversationType::from_ffi(1), Some(ConversationType::Group));
assert_eq!(ConversationType::from_ffi(2), Some(ConversationType::Sync));
assert_eq!(
ConversationType::from_ffi(3),
Some(ConversationType::Oneshot)
);
assert_eq!(ConversationType::from_ffi(4), None);
assert_eq!(ConsentState::from_ffi(0), Some(ConsentState::Unknown));
assert_eq!(ConsentState::from_ffi(1), Some(ConsentState::Allowed));
assert_eq!(ConsentState::from_ffi(2), Some(ConsentState::Denied));
assert_eq!(ConsentState::from_ffi(-1), None);
assert_eq!(
ConversationOrderBy::from_ffi(0),
Some(ConversationOrderBy::CreatedAt)
);
assert_eq!(
ConversationOrderBy::from_ffi(1),
Some(ConversationOrderBy::LastActivity)
);
assert_eq!(ConversationOrderBy::from_ffi(-1), None);
}
#[test]
fn ffi_discriminants_message_and_preference() {
assert_eq!(MessageKind::from_ffi(0), Some(MessageKind::Application));
assert_eq!(
MessageKind::from_ffi(1),
Some(MessageKind::MembershipChange)
);
assert_eq!(MessageKind::from_ffi(2), None);
assert_eq!(
DeliveryStatus::from_ffi(0),
Some(DeliveryStatus::Unpublished)
);
assert_eq!(DeliveryStatus::from_ffi(1), Some(DeliveryStatus::Published));
assert_eq!(DeliveryStatus::from_ffi(2), Some(DeliveryStatus::Failed));
assert_eq!(DeliveryStatus::from_ffi(3), None);
assert_eq!(SortDirection::from_ffi(0), Some(SortDirection::Ascending));
assert_eq!(SortDirection::from_ffi(1), Some(SortDirection::Descending));
assert_eq!(SortDirection::from_ffi(2), None);
assert_eq!(PreferenceKind::from_ffi(0), Some(PreferenceKind::Consent));
assert_eq!(PreferenceKind::from_ffi(1), Some(PreferenceKind::HmacKey));
assert_eq!(PreferenceKind::from_ffi(2), None);
}
#[test]
fn permission_policy_discriminants_and_write_offset() {
assert_eq!(PermissionPolicy::from_ffi(0), Some(PermissionPolicy::Allow));
assert_eq!(PermissionPolicy::from_ffi(1), Some(PermissionPolicy::Deny));
assert_eq!(
PermissionPolicy::from_ffi(2),
Some(PermissionPolicy::AdminOnly)
);
assert_eq!(
PermissionPolicy::from_ffi(3),
Some(PermissionPolicy::SuperAdminOnly)
);
assert_eq!(
PermissionPolicy::from_ffi(4),
Some(PermissionPolicy::DoesNotExist)
);
assert_eq!(PermissionPolicy::from_ffi(5), Some(PermissionPolicy::Other));
assert_eq!(PermissionPolicy::from_ffi(99), None);
assert_eq!(PermissionPolicy::Allow.to_write_i32(), 1);
assert_eq!(PermissionPolicy::Deny.to_write_i32(), 2);
assert_eq!(PermissionPolicy::SuperAdminOnly.to_write_i32(), 4);
}
}