diff --git a/backend/canisters/user/CHANGELOG.md b/backend/canisters/user/CHANGELOG.md index 5a377a5cfd..6eb79e5d55 100644 --- a/backend/canisters/user/CHANGELOG.md +++ b/backend/canisters/user/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] +### Added + +- Set/store user's wallet configuration ([#6242](https://github.com/open-chat-labs/open-chat/pull/6242)) + ### Changed - Configure message visibility to non-members of public channels/groups ([#6152](https://github.com/open-chat-labs/open-chat/pull/6152)) diff --git a/backend/canisters/user/api/can.did b/backend/canisters/user/api/can.did index 6e58e0b7dc..8786232b16 100644 --- a/backend/canisters/user/api/can.did +++ b/backend/canisters/user/api/can.did @@ -886,6 +886,7 @@ type InitialStateResponse = variant { streak_ends : TimestampMillis; next_daily_claim : TimestampMillis; is_unique_person : bool; + wallet_config: WalletConfig; }; }; @@ -948,6 +949,7 @@ type UpdatesResponse = variant { streak_ends : TimestampMillis; next_daily_claim : TimestampMillis; is_unique_person : opt bool; + wallet_config: opt WalletConfig; }; SuccessNoUpdates; }; @@ -1189,6 +1191,27 @@ type RetrieveBtcResponse = variant { InternalError : text; }; +type ConfigureWalletArgs = record { + config : WalletConfig; +}; + +type WalletConfig = variant { + Auto : AutoWallet; + Manual : ManualWallet; +}; + +type AutoWallet = record { + min_cents_visible : nat32; +}; + +type ManualWallet = record { + tokens : vec CanisterId; +}; + +type ConfigureWalletResponse = variant { + Success; +}; + service : { send_message_v2 : (SendMessageV2Args) -> (SendMessageResponse); edit_message_v2 : (EditMessageV2Args) -> (EditMessageResponse); @@ -1236,6 +1259,7 @@ service : { join_video_call : (JoinVideoCallArgs) -> (JoinVideoCallResponse); end_video_call : (EndVideoCallArgs) -> (EndVideoCallResponse); claim_daily_chit : (EmptyArgs) -> (ClaimDailyChitResponse); + configure_wallet : (ConfigureWalletArgs) -> (ConfigureWalletResponse); events : (EventsArgs) -> (EventsResponse) query; events_by_index : (EventsByIndexArgs) -> (EventsResponse) query; diff --git a/backend/canisters/user/api/src/lib.rs b/backend/canisters/user/api/src/lib.rs index cba752b750..de03c0e9f8 100644 --- a/backend/canisters/user/api/src/lib.rs +++ b/backend/canisters/user/api/src/lib.rs @@ -297,3 +297,25 @@ pub struct NamedAccount { pub name: String, pub account: String, } + +#[derive(CandidType, Serialize, Deserialize, Clone, Debug)] +pub enum WalletConfig { + Auto(AutoWallet), + Manual(ManualWallet), +} + +#[derive(CandidType, Serialize, Deserialize, Clone, Debug)] +pub struct AutoWallet { + min_cents_visible: u32, +} + +#[derive(CandidType, Serialize, Deserialize, Clone, Debug)] +pub struct ManualWallet { + tokens: Vec, +} + +impl Default for WalletConfig { + fn default() -> Self { + WalletConfig::Auto(AutoWallet { min_cents_visible: 100 }) + } +} diff --git a/backend/canisters/user/api/src/main.rs b/backend/canisters/user/api/src/main.rs index 7e1e845de7..ad0f16796a 100644 --- a/backend/canisters/user/api/src/main.rs +++ b/backend/canisters/user/api/src/main.rs @@ -29,6 +29,7 @@ fn main() { generate_candid_method!(user, cancel_message_reminder, update); generate_candid_method!(user, cancel_p2p_swap, update); generate_candid_method!(user, claim_daily_chit, update); + generate_candid_method!(user, configure_wallet, update); generate_candid_method!(user, create_community, update); generate_candid_method!(user, create_group, update); generate_candid_method!(user, delete_community, update); diff --git a/backend/canisters/user/api/src/queries/initial_state.rs b/backend/canisters/user/api/src/queries/initial_state.rs index 5b466f785f..257cefe1a1 100644 --- a/backend/canisters/user/api/src/queries/initial_state.rs +++ b/backend/canisters/user/api/src/queries/initial_state.rs @@ -2,6 +2,8 @@ use candid::CandidType; use serde::{Deserialize, Serialize}; use types::{CanisterId, Chat, ChatId, ChitEarned, DirectChatSummary, Empty, GroupChatSummary, TimestampMillis, UserId}; +use crate::WalletConfig; + pub type Args = Empty; #[derive(CandidType, Serialize, Deserialize, Debug)] @@ -29,6 +31,7 @@ pub struct SuccessResult { pub streak_ends: TimestampMillis, pub next_daily_claim: TimestampMillis, pub is_unique_person: bool, + pub wallet_config: WalletConfig, } #[derive(CandidType, Serialize, Deserialize, Debug)] diff --git a/backend/canisters/user/api/src/queries/updates.rs b/backend/canisters/user/api/src/queries/updates.rs index dcb5d52884..cc8e72b20d 100644 --- a/backend/canisters/user/api/src/queries/updates.rs +++ b/backend/canisters/user/api/src/queries/updates.rs @@ -1,4 +1,4 @@ -use crate::initial_state::PinNumberSettings; +use crate::{initial_state::PinNumberSettings, WalletConfig}; use candid::CandidType; use serde::{Deserialize, Serialize}; use types::{ @@ -38,6 +38,7 @@ pub struct SuccessResult { pub streak_ends: TimestampMillis, pub next_daily_claim: TimestampMillis, pub is_unique_person: Option, + pub wallet_config: Option, } #[derive(CandidType, Serialize, Deserialize, Clone, Debug)] diff --git a/backend/canisters/user/api/src/updates/configure_wallet.rs b/backend/canisters/user/api/src/updates/configure_wallet.rs new file mode 100644 index 0000000000..70d6a0f5f7 --- /dev/null +++ b/backend/canisters/user/api/src/updates/configure_wallet.rs @@ -0,0 +1,13 @@ +use crate::WalletConfig; +use candid::CandidType; +use serde::{Deserialize, Serialize}; + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub struct Args { + pub config: WalletConfig, +} + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub enum Response { + Success, +} diff --git a/backend/canisters/user/api/src/updates/mod.rs b/backend/canisters/user/api/src/updates/mod.rs index f50d3bd606..e2cf6a705f 100644 --- a/backend/canisters/user/api/src/updates/mod.rs +++ b/backend/canisters/user/api/src/updates/mod.rs @@ -23,6 +23,7 @@ pub mod c2c_vote_on_proposal; pub mod cancel_message_reminder; pub mod cancel_p2p_swap; pub mod claim_daily_chit; +pub mod configure_wallet; pub mod create_community; pub mod create_group; pub mod delete_community; diff --git a/backend/canisters/user/impl/src/lib.rs b/backend/canisters/user/impl/src/lib.rs index 04bc587dd7..95e0f81e1a 100644 --- a/backend/canisters/user/impl/src/lib.rs +++ b/backend/canisters/user/impl/src/lib.rs @@ -30,7 +30,7 @@ use types::{ Achievement, BuildVersion, CanisterId, Chat, ChatId, ChatMetrics, ChitEarned, ChitEarnedReason, CommunityId, Cryptocurrency, Cycles, Document, Notification, TimestampMillis, Timestamped, UniquePersonProof, UserId, }; -use user_canister::{NamedAccount, UserCanisterEvent}; +use user_canister::{NamedAccount, UserCanisterEvent, WalletConfig}; use utils::canister_event_sync_queue::CanisterEventSyncQueue; use utils::env::Environment; use utils::regular_jobs::RegularJobs; @@ -226,6 +226,8 @@ struct Data { pub achievements: HashSet, pub achievements_last_seen: TimestampMillis, pub unique_person_proof: Option, + #[serde(default)] + pub wallet_config: Timestamped, pub rng_seed: [u8; 32], } @@ -289,6 +291,7 @@ impl Data { achievements_last_seen: 0, unique_person_proof: None, rng_seed: [0; 32], + wallet_config: Timestamped::default(), } } diff --git a/backend/canisters/user/impl/src/lifecycle/post_upgrade.rs b/backend/canisters/user/impl/src/lifecycle/post_upgrade.rs index f734b9b6ff..2e14dc0e8d 100644 --- a/backend/canisters/user/impl/src/lifecycle/post_upgrade.rs +++ b/backend/canisters/user/impl/src/lifecycle/post_upgrade.rs @@ -1,14 +1,15 @@ use crate::lifecycle::{init_env, init_state}; use crate::memory::get_upgrades_memory; -use crate::{read_state, Data}; +use crate::{mutate_state, read_state, Data}; use canister_logger::LogEntry; use canister_tracing_macros::trace; use ic_cdk::post_upgrade; use stable_memory::get_reader; use std::time::Duration; use tracing::info; -use types::{Empty, Milliseconds}; +use types::{Empty, Milliseconds, Timestamped}; use user_canister::post_upgrade::Args; +use user_canister::WalletConfig; use utils::time::DAY_IN_MS; const SIX_MONTHS: Milliseconds = 183 * DAY_IN_MS; @@ -43,6 +44,11 @@ fn post_upgrade(args: Args) { } } }); + + // TODO: Remove this after the next release + mutate_state(|state| { + state.data.wallet_config = Timestamped::new(WalletConfig::default(), state.env.now()); + }); } fn mark_user_canister_empty() { diff --git a/backend/canisters/user/impl/src/queries/initial_state.rs b/backend/canisters/user/impl/src/queries/initial_state.rs index 5317274342..50355b81ca 100644 --- a/backend/canisters/user/impl/src/queries/initial_state.rs +++ b/backend/canisters/user/impl/src/queries/initial_state.rs @@ -55,5 +55,6 @@ fn initial_state_impl(state: &RuntimeState) -> Response { streak_ends: state.data.streak.ends(), next_daily_claim: if state.data.streak.can_claim(now) { today(now) } else { tomorrow(now) }, is_unique_person: state.data.unique_person_proof.is_some(), + wallet_config: state.data.wallet_config.value.clone(), }) } diff --git a/backend/canisters/user/impl/src/queries/updates.rs b/backend/canisters/user/impl/src/queries/updates.rs index 01b0f35dac..0ada1a8052 100644 --- a/backend/canisters/user/impl/src/queries/updates.rs +++ b/backend/canisters/user/impl/src/queries/updates.rs @@ -41,12 +41,15 @@ fn updates_impl(updates_since: TimestampMillis, state: &RuntimeState) -> Respons .as_ref() .is_some_and(|p| p.timestamp > updates_since); + let wallet_config = state.data.wallet_config.if_set_after(updates_since).cloned(); + let has_any_updates = username.is_some() || display_name.has_update() || avatar_id.has_update() || blocked_users.is_some() || avatar_id.has_update() || suspended.is_some() + || wallet_config.is_some() || pin_number_updated || is_unique_person_updated || state.data.direct_chats.any_updated(updates_since) @@ -166,5 +169,6 @@ fn updates_impl(updates_since: TimestampMillis, state: &RuntimeState) -> Respons streak_ends, next_daily_claim, is_unique_person, + wallet_config, }) } diff --git a/backend/canisters/user/impl/src/updates/configure_wallet.rs b/backend/canisters/user/impl/src/updates/configure_wallet.rs new file mode 100644 index 0000000000..076d019820 --- /dev/null +++ b/backend/canisters/user/impl/src/updates/configure_wallet.rs @@ -0,0 +1,19 @@ +use crate::guards::caller_is_owner; +use crate::{mutate_state, run_regular_jobs, RuntimeState}; +use canister_tracing_macros::trace; +use ic_cdk::update; +use types::Timestamped; +use user_canister::configure_wallet::{Response::*, *}; + +#[update(guard = "caller_is_owner")] +#[trace] +fn configure_wallet(args: Args) -> Response { + run_regular_jobs(); + + mutate_state(|state| configure_wallet_impl(args, state)) +} + +fn configure_wallet_impl(args: Args, state: &mut RuntimeState) -> Response { + state.data.wallet_config = Timestamped::new(args.config, state.env.now()); + Success +} diff --git a/backend/canisters/user/impl/src/updates/mod.rs b/backend/canisters/user/impl/src/updates/mod.rs index e2a2de5c88..519c2cce76 100644 --- a/backend/canisters/user/impl/src/updates/mod.rs +++ b/backend/canisters/user/impl/src/updates/mod.rs @@ -24,6 +24,7 @@ pub mod c2c_vote_on_proposal; pub mod cancel_message_reminder; pub mod cancel_p2p_swap; pub mod claim_daily_chit; +pub mod configure_wallet; pub mod create_community; pub mod create_group; pub mod delete_community;