Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c93b5b4
feat(console): support for raw canister (proof of concept)
peterpeterparker Jan 6, 2026
932f1fe
Merge branch 'main' into feat/raw-canister
peterpeterparker Jan 6, 2026
8d939a3
feat: use new functions
peterpeterparker Jan 6, 2026
ea3c432
feat: finish support for create canister
peterpeterparker Jan 6, 2026
8d01ec1
feat: finish support for create canister
peterpeterparker Jan 6, 2026
982267c
fix: export and impl missing
peterpeterparker Jan 6, 2026
9a4369e
feat: generate did
peterpeterparker Jan 6, 2026
5af1f36
feat: upgrade state
peterpeterparker Jan 6, 2026
c0aa3de
chore: no dev
peterpeterparker Jan 6, 2026
b61aac5
Merge branch 'main' into feat/raw-canister
peterpeterparker Jan 6, 2026
77efb49
Merge branch 'main' into feat/raw-canister
peterpeterparker Jan 6, 2026
1fa08eb
fix: upgrade
peterpeterparker Jan 6, 2026
bcef1fd
feat: create canister
peterpeterparker Jan 6, 2026
e4cfa18
Merge branch 'main' into feat/raw-canister
peterpeterparker Jan 6, 2026
00e72d5
feat: canister id schema
peterpeterparker Jan 6, 2026
8643017
Merge branch 'main' into feat/raw-canister
peterpeterparker Jan 6, 2026
2f98e89
feat: create canister with optional name
peterpeterparker Jan 6, 2026
5826761
feat: canister overview
peterpeterparker Jan 6, 2026
8e7576e
fix: load list of canister
peterpeterparker Jan 6, 2026
7018b0e
fix: few cast issues
peterpeterparker Jan 6, 2026
dbf7496
feat: canister settings
peterpeterparker Jan 6, 2026
d151785
feat: menu for showcase
peterpeterparker Jan 6, 2026
16f07d0
chore: fmt
peterpeterparker Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/console/console.did
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type Controller = record {
expires_at : opt nat64;
};
type ControllerScope = variant { Write; Admin; Submit };
type CreateCanisterArgs = record { subnet_id : opt principal; name : opt text };
type CreateMissionControlArgs = record { subnet_id : opt principal };
type CreateOrbiterArgs = record {
block_index : opt nat64;
Expand All @@ -92,6 +93,12 @@ type CreateSatelliteArgs = record {
name : opt text;
user : principal;
};
type CreateSegmentArgs = variant {
Orbiter : CreateOrbiterArgs;
MissionControl : CreateMissionControlArgs;
Canister : CreateCanisterArgs;
Satellite : CreateSatelliteArgs;
};
type CustomDomain = record {
updated_at : nat64;
created_at : nat64;
Expand Down Expand Up @@ -299,7 +306,7 @@ type SegmentKey = record {
segment_id : principal;
segment_kind : StorableSegmentKind;
};
type SegmentKind = variant { Orbiter; MissionControl; Satellite };
type SegmentKind = variant { Orbiter; MissionControl; Canister; Satellite };
type SegmentsDeploymentOptions = record {
orbiter : opt text;
mission_control_version : opt text;
Expand Down Expand Up @@ -340,7 +347,7 @@ type SetStorageConfig = record {
redirects : opt vec record { text; StorageConfigRedirect };
};
type SignedDelegation = record { signature : blob; delegation : Delegation };
type StorableSegmentKind = variant { Orbiter; Satellite };
type StorableSegmentKind = variant { Orbiter; Canister; Satellite };
type StorageConfig = record {
iframe : opt StorageConfigIFrame;
updated_at : opt nat64;
Expand Down Expand Up @@ -403,6 +410,7 @@ service : () -> {
create_mission_control : (CreateMissionControlArgs) -> (principal);
create_orbiter : (CreateOrbiterArgs) -> (principal);
create_satellite : (CreateSatelliteArgs) -> (principal);
create_segment : (CreateSegmentArgs) -> (principal);
del_controllers : (DeleteControllersArgs) -> ();
del_custom_domain : (text) -> ();
delete_proposal_assets : (DeleteProposalAssets) -> ();
Expand Down
19 changes: 18 additions & 1 deletion src/console/src/api/factory.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::factory::canister::create_canister;
use crate::factory::mission_control::create_mission_control as create_mission_control_console;
use crate::factory::orbiter::create_orbiter as create_orbiter_console;
use crate::factory::satellite::create_satellite as create_satellite_console;
Expand All @@ -6,7 +7,7 @@ use ic_cdk_macros::update;
use junobuild_shared::ic::api::caller;
use junobuild_shared::ic::UnwrapOrTrap;
use junobuild_shared::types::interface::{
CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs,
CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs, CreateSegmentArgs,
};

#[update]
Expand All @@ -33,3 +34,19 @@ async fn create_orbiter(args: CreateOrbiterArgs) -> Principal {

create_orbiter_console(caller, args).await.unwrap_or_trap()
}

#[update]
async fn create_segment(args: CreateSegmentArgs) -> Principal {
let caller = caller();

let result = match args {
CreateSegmentArgs::Satellite(args) => create_satellite_console(caller, args).await,
CreateSegmentArgs::MissionControl(args) => {
create_mission_control_console(caller, args).await
}
CreateSegmentArgs::Orbiter(args) => create_orbiter_console(caller, args).await,
CreateSegmentArgs::Canister(args) => create_canister(caller, args).await,
};

result.unwrap_or_trap()
}
1 change: 1 addition & 0 deletions src/console/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub const SATELLITE_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s(
pub const ORBITER_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s(3_000_000_000_000);
pub const MISSION_CONTROL_CREATION_FEE_CYCLES: CyclesTokens =
CyclesTokens::from_e12s(3_000_000_000_000);
pub const CANISTER_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s(3_000_000_000_000);

// 1 ICP but also the default credit - i.e. a mission control starts with one credit.
// A credit which can be used to start one satellite or one orbiter.
Expand Down
83 changes: 83 additions & 0 deletions src/console/src/factory/canister.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::accounts::get_existing_account;
use crate::constants::FREEZING_THRESHOLD_ONE_YEAR;
use crate::factory::orchestrator::create_segment_with_account;
use crate::factory::services::payment::{process_payment_cycles, refund_payment_cycles};
use crate::factory::types::CanisterCreator;
use crate::factory::utils::controllers::update_mission_control_controllers;
use crate::fees::get_factory_fee;
use crate::rates::increment_canister_rate;
use crate::segments::add_segment as add_segment_store;
use crate::types::ledger::Fee;
use crate::types::state::{Segment, SegmentKey, StorableSegmentKind};
use candid::{Nat, Principal};
use junobuild_shared::constants::shared::CREATE_CANISTER_CYCLES;
use junobuild_shared::ic::api::id;
use junobuild_shared::mgmt::cmc::create_canister_with_cmc;
use junobuild_shared::mgmt::ic::create_canister_with_ic_mgmt;
use junobuild_shared::mgmt::types::cmc::SubnetId;
use junobuild_shared::mgmt::types::ic::CreateCanisterInitSettingsArg;
use junobuild_shared::types::interface::CreateCanisterArgs;
use junobuild_shared::types::state::{SegmentKind, UserId};

pub async fn create_canister(
caller: Principal,
args: CreateCanisterArgs,
) -> Result<Principal, String> {
let account = get_existing_account(&caller)?;

let name = args.name.clone();
let creator: CanisterCreator = CanisterCreator::User((account.owner, None));

let fee = get_factory_fee(&SegmentKind::Canister)?.fee_cycles;

let canister_id = create_segment_with_account(
create_raw_canister,
process_payment_cycles,
refund_payment_cycles,
&increment_canister_rate,
Fee::Cycles(fee),
&account,
creator,
args.into(),
)
.await?;

add_segment(&account.owner, &canister_id, &name);

Ok(canister_id)
}

async fn create_raw_canister(
creator: CanisterCreator,
subnet_id: Option<SubnetId>,
) -> Result<Principal, String> {
let CanisterCreator::User((user_id, _)) = creator else {
return Err("Mission Control cannot create a raw canister".to_string());
};

// We temporarily use the Console as a controller to create the canister but
// remove it as soon as it is spin.
let temporary_init_controllers = Vec::from([id(), user_id]);

let create_settings_arg = CreateCanisterInitSettingsArg {
controllers: temporary_init_controllers,
freezing_threshold: Nat::from(FREEZING_THRESHOLD_ONE_YEAR),
};

let mission_control_id = if let Some(subnet_id) = subnet_id {
create_canister_with_cmc(&create_settings_arg, CREATE_CANISTER_CYCLES, &subnet_id).await
} else {
create_canister_with_ic_mgmt(&create_settings_arg, CREATE_CANISTER_CYCLES).await
}?;

update_mission_control_controllers(&mission_control_id, &user_id).await?;

Ok(mission_control_id)
}

fn add_segment(user: &UserId, canister_id: &Principal, name: &Option<String>) {
let metadata = Segment::init_metadata(name);
let canister = Segment::new(canister_id, Some(metadata));
let key = SegmentKey::from(user, canister_id, StorableSegmentKind::Canister);
add_segment_store(&key, &canister)
}
13 changes: 12 additions & 1 deletion src/console/src/factory/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::factory::types::CanisterCreator;
use crate::factory::types::CreateSegmentArgs;
use candid::Principal;
use junobuild_shared::types::interface::{
CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs,
CreateCanisterArgs, CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs,
};
use junobuild_shared::types::state::{ControllerId, UserId};

Expand Down Expand Up @@ -63,3 +63,14 @@ impl From<CreateMissionControlArgs> for CreateSegmentArgs {
}
}
}

impl From<CreateCanisterArgs> for CreateSegmentArgs {
fn from(args: CreateCanisterArgs) -> Self {
Self {
// Unlike Satellite and Orbiter, or same as Mission Control, Canister can only be
// spin using credits or ICRC-2 transfer from.
block_index: None,
subnet_id: args.subnet_id,
}
}
}
1 change: 1 addition & 0 deletions src/console/src/factory/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod canister;
mod impls;
pub mod mission_control;
pub mod orbiter;
Expand Down
12 changes: 10 additions & 2 deletions src/console/src/fees/init.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::constants::{
MISSION_CONTROL_CREATION_FEE_CYCLES, ORBITER_CREATION_FEE_CYCLES, ORBITER_CREATION_FEE_ICP,
SATELLITE_CREATION_FEE_CYCLES, SATELLITE_CREATION_FEE_ICP,
CANISTER_CREATION_FEE_CYCLES, MISSION_CONTROL_CREATION_FEE_CYCLES, ORBITER_CREATION_FEE_CYCLES,
ORBITER_CREATION_FEE_ICP, SATELLITE_CREATION_FEE_CYCLES, SATELLITE_CREATION_FEE_ICP,
};
use crate::types::state::{FactoryFee, FactoryFees};
use ic_cdk::api::time;
Expand Down Expand Up @@ -35,5 +35,13 @@ pub fn init_factory_fees() -> FactoryFees {
updated_at: now,
},
),
(
SegmentKind::Canister,
FactoryFee {
fee_cycles: CANISTER_CREATION_FEE_CYCLES,
fee_icp: None,
updated_at: now,
},
),
])
}
1 change: 1 addition & 0 deletions src/console/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ use junobuild_shared::types::domain::CustomDomains;
use junobuild_shared::types::interface::CreateMissionControlArgs;
use junobuild_shared::types::interface::CreateOrbiterArgs;
use junobuild_shared::types::interface::CreateSatelliteArgs;
use junobuild_shared::types::interface::CreateSegmentArgs;
use junobuild_shared::types::interface::{
AssertMissionControlCenterArgs, DeleteControllersArgs, GetCreateCanisterFeeArgs,
SetControllersArgs,
Expand Down
5 changes: 4 additions & 1 deletion src/console/src/memory/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::fees::init_factory_fees;
use crate::memory::manager::{get_memory_upgrades, init_stable_state, STATE};
use crate::rates::init::init_factory_rates;
use crate::types::state::{HeapState, ReleasesMetadata, State};
use crate::upgrade::upgrade_init_factory_fees_and_rates;
use crate::upgrade::{upgrade_init_canister_fees_and_rates, upgrade_init_factory_fees_and_rates};
use ciborium::{from_reader, into_writer};
use ic_cdk_macros::{init, post_upgrade, pre_upgrade};
use junobuild_shared::ic::api::caller;
Expand Down Expand Up @@ -60,4 +60,7 @@ fn post_upgrade() {

// TODO: to be removed, one time upgrade
upgrade_init_factory_fees_and_rates();

// TODO: to be removed, one time upgrade
upgrade_init_canister_fees_and_rates();
}
7 changes: 7 additions & 0 deletions src/console/src/rates/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,12 @@ pub fn init_factory_rates() -> FactoryRates {
tokens: tokens.clone(),
},
),
(
SegmentKind::Canister,
FactoryRate {
config: DEFAULT_RATE_CONFIG,
tokens: tokens.clone(),
},
),
])
}
4 changes: 4 additions & 0 deletions src/console/src/rates/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ pub fn increment_mission_controls_rate() -> Result<(), String> {
pub fn increment_orbiters_rate() -> Result<(), String> {
increment_rate(&SegmentKind::Orbiter)
}

pub fn increment_canister_rate() -> Result<(), String> {
increment_rate(&SegmentKind::Canister)
}
2 changes: 1 addition & 1 deletion src/console/src/segments/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ fn filter_segments_range(
let end_key = SegmentKey {
user: *user,
// Fallback to last enum
segment_kind: segment_kind.clone().unwrap_or(StorableSegmentKind::Orbiter),
segment_kind: segment_kind.clone().unwrap_or(StorableSegmentKind::Canister),
segment_id: segment_id.unwrap_or(PRINCIPAL_MAX),
};

Expand Down
1 change: 1 addition & 0 deletions src/console/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ pub mod state {
// For historical reasons, MissionControl is not stored in the segments stable tree
// but within the Account structure
Orbiter,
Canister,
}
}

Expand Down
56 changes: 55 additions & 1 deletion src/console/src/upgrade.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use crate::constants::CANISTER_CREATION_FEE_CYCLES;
use crate::fees::init_factory_fees;
use crate::rates::init::init_factory_rates;
use crate::store::mutate_heap_state;
use crate::types::state::HeapState;
use crate::types::state::{FactoryFee, FactoryFees, FactoryRate, FactoryRates, HeapState};
use ic_cdk::api::time;
use junobuild_shared::ic::api::print;
use junobuild_shared::rate::constants::DEFAULT_RATE_CONFIG;
use junobuild_shared::rate::types::RateTokens;
use junobuild_shared::types::state::SegmentKind;

pub fn upgrade_init_factory_fees_and_rates() {
mutate_heap_state(|state| upgrade_init_factory_fees_and_rates_impl(state))
Expand All @@ -11,3 +17,51 @@ fn upgrade_init_factory_fees_and_rates_impl(state: &mut HeapState) {
state.factory_fees.get_or_insert_with(init_factory_fees);
state.factory_rates.get_or_insert_with(init_factory_rates);
}

pub fn upgrade_init_canister_fees_and_rates() {
mutate_heap_state(|state| {
upgrade_init_factory_fees_and_rates_impl(state);

upgrade_canister_fees(&mut state.factory_fees)
.unwrap_or_else(|err| print(format!("Error upgrading the Canister fee: {:?}", err)));

upgrade_canister_rates(&mut state.factory_rates)
.unwrap_or_else(|err| print(format!("Error upgrading the Canister rate: {:?}", err)));
});
}

fn upgrade_canister_fees(factory_fees: &mut Option<FactoryFees>) -> Result<(), String> {
let fees = factory_fees
.as_mut()
.ok_or_else(|| "Factory fees not initialized".to_string())?;

let fee = FactoryFee {
fee_cycles: CANISTER_CREATION_FEE_CYCLES,
fee_icp: None,
updated_at: time(),
};

fees.insert(SegmentKind::Canister, fee);

Ok(())
}

fn upgrade_canister_rates(factory_rates: &mut Option<FactoryRates>) -> Result<(), String> {
let rates = factory_rates
.as_mut()
.ok_or_else(|| "Factory rates not initialized".to_string())?;

let tokens: RateTokens = RateTokens {
tokens: 1,
updated_at: time(),
};

let rate = FactoryRate {
config: DEFAULT_RATE_CONFIG,
tokens: tokens.clone(),
};

rates.insert(SegmentKind::Canister, rate);

Ok(())
}
Loading
Loading