Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
114 changes: 114 additions & 0 deletions contracts/shade/src/components/cross_chain_pledge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use soroban_sdk::{panic_with_error, Address, Env, String, Vec};

use crate::{
errors::ContractError,
events,
types::{CrossChainPledge, CrossChainPledgeStatus, DataKey},
};

pub fn create_pledge(
env: &Env,
source_chain: String,
source_pledge_id: u64,
destination_chain: String,
merchant: Address,
payer: Address,
token: Address,
amount: i128,
memo: Option<String>,
) -> u64 {
merchant.require_auth();

let pledge_id = get_next_pledge_id(env);
let now = env.ledger().timestamp();

let pledge = CrossChainPledge {
id: pledge_id,
source_chain: source_chain.clone(),
source_pledge_id,
destination_chain,
merchant: merchant.clone(),
payer,
token,
amount,
status: CrossChainPledgeStatus::Pending,
created_at: now,
updated_at: now,
memo,
};

env.storage()
.persistent()
.set(&DataKey::CrossChainPledge(pledge_id), &pledge);
env.storage()
.persistent()
.set(&DataKey::PledgeIdBySourceChain(source_chain, source_pledge_id), &pledge_id);

events::publish_cross_chain_pledge_created(env, &pledge);

pledge_id
}

pub fn update_pledge_status(
env: &Env,
pledge_id: u64,
new_status: CrossChainPledgeStatus,
) {
let admin = crate::components::core::get_admin(env);
admin.require_auth();

let mut pledge = get_pledge(env, pledge_id);
pledge.status = new_status;
pledge.updated_at = env.ledger().timestamp();

env.storage()
.persistent()
.set(&DataKey::CrossChainPledge(pledge_id), &pledge);

events::publish_cross_chain_pledge_updated(env, &pledge);
}

pub fn get_pledge(env: &Env, pledge_id: u64) -> CrossChainPledge {
env.storage()
.persistent()
.get(&DataKey::CrossChainPledge(pledge_id))
.unwrap_or_else(|| panic_with_error!(env, ContractError::NotFound))
}

pub fn get_pledge_by_source(
env: &Env,
source_chain: String,
source_pledge_id: u64,
) -> CrossChainPledge {
let pledge_id: u64 = env
.storage()
.persistent()
.get(&DataKey::PledgeIdBySourceChain(source_chain, source_pledge_id))
.unwrap_or_else(|| panic_with_error!(env, ContractError::NotFound));
get_pledge(env, pledge_id)
}

fn get_next_pledge_id(env: &Env) -> u64 {
let count: u64 = env
.storage()
.persistent()
.get(&DataKey::CrossChainPledgeCount)
.unwrap_or(0);
env.storage()
.persistent()
.set(&DataKey::CrossChainPledgeCount, &(count + 1));
count
}

pub fn get_all_pledges(env: &Env) -> Vec<CrossChainPledge> {
let count: u64 = env
.storage()
.persistent()
.get(&DataKey::CrossChainPledgeCount)
.unwrap_or(0);
let mut pledges = Vec::new(env);
for i in 0..count {
pledges.push_back(get_pledge(env, i));
}
pledges
}
3 changes: 1 addition & 2 deletions contracts/shade/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,4 @@ pub mod upgrade;
pub mod escrow;
pub mod backer_rewards;
pub mod event;
pub mod campaign;
pub mod nft;
pub mod cross_chain_pledge;
1 change: 1 addition & 0 deletions contracts/shade/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub enum ContractError {
TicketNotFound = 51,
NotTicketOwner = 52,
InvalidResalePrice = 54,
NotFound = 55,
// ── Multi-sig massive withdrawal ─────────────────────────────────────────
/// The withdrawal amount is below the configured threshold; no multi-sig needed.
BelowMultiSigThreshold = 55,
Expand Down
43 changes: 43 additions & 0 deletions contracts/shade/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,33 @@ pub fn publish_bridge_placeholder_event(
.publish(env);
}

#[contractevent]
pub struct CrossChainPledgeCreatedEvent {
pub pledge_id: u64,
pub source_chain: String,
pub source_pledge_id: u64,
pub merchant: Address,
pub payer: Address,
pub token: Address,
pub amount: i128,
pub timestamp: u64,
}

pub fn publish_cross_chain_pledge_created(
env: &Env,
pledge: &crate::types::CrossChainPledge,
) {
CrossChainPledgeCreatedEvent {
pledge_id: pledge.id,
source_chain: pledge.source_chain.clone(),
source_pledge_id: pledge.source_pledge_id,
merchant: pledge.merchant.clone(),
payer: pledge.payer.clone(),
token: pledge.token.clone(),
amount: pledge.amount,
timestamp: pledge.created_at,
}
}
// ── Bridge listener / external deposit events ─────────────────────────────────

#[contractevent]
Expand Down Expand Up @@ -952,6 +979,22 @@ pub fn publish_upgrade_vote_cast_event(
}

#[contractevent]
pub struct CrossChainPledgeUpdatedEvent {
pub pledge_id: u64,
pub status: crate::types::CrossChainPledgeStatus,
pub timestamp: u64,
}

pub fn publish_cross_chain_pledge_updated(
env: &Env,
pledge: &crate::types::CrossChainPledge,
) {
CrossChainPledgeUpdatedEvent {
pledge_id: pledge.id,
status: pledge.status.clone(),
timestamp: pledge.updated_at,
}
}
pub struct UpgradeProposalFinalizedEvent {
pub proposal_id: u64,
pub executor: Address,
Expand Down
64 changes: 22 additions & 42 deletions contracts/shade/src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::types::{
CrossChainBridgePayload, CrossChainPledge, CrossChainPledgeStatus, Event, Invoice, InvoiceFilter, Merchant, MerchantAnalytics,
MerchantAnalyticsSummary, MerchantFilter, OracleConfig, PaymentPayload, PendingFee, Role,
Subscription, SubscriptionPlan, Ticket, TokenAnalytics, Transaction
CrossChainBridgePayload, Event, EventFilter, Invoice, InvoiceFilter, InvoicePage, Merchant,
MerchantFilter, MerchantPage, MerchantAnalytics, MerchantAnalyticsSummary, OracleConfig,
PaymentPayload, PendingFee, Role, Subscription, SubscriptionFilter, SubscriptionPlan,
Expand Down Expand Up @@ -493,56 +496,33 @@ pub trait ShadeTrait {
/// Create a crowdfunding campaign for tiered backer rewards.
fn create_backer_campaign(
env: Env,
source_chain: String,
source_pledge_id: u64,
destination_chain: String,
merchant: Address,
name: String,
payer: Address,
token: Address,
deadline: u64,
amount: i128,
memo: Option<String>,
) -> u64;

fn get_backer_campaign(env: Env, campaign_id: u64) -> BackerCampaign;

/// Define reward tiers with perks. Tiers must have ascending `min_pledge`.
fn set_backer_reward_tiers(
env: Env,
merchant: Address,
campaign_id: u64,
tiers: Vec<BackerRewardTier>,
);

fn get_backer_reward_tiers(env: Env, campaign_id: u64) -> Vec<BackerRewardTier>;

/// Record a backer pledge to a campaign.
fn pledge_to_campaign(env: Env, backer: Address, campaign_id: u64, amount: i128);

fn get_backer_pledge(env: Env, campaign_id: u64, backer: Address) -> i128;

/// Select a reward tier. Backer's total pledge must meet the tier minimum.
fn select_backer_reward_tier(
/// Update the status of a cross-chain pledge
fn update_cross_chain_pledge_status(
env: Env,
backer: Address,
campaign_id: u64,
tier_index: u32,
pledge_id: u64,
new_status: CrossChainPledgeStatus,
);

fn get_backer_selected_tier(env: Env, campaign_id: u64, backer: Address) -> Option<u32>;
/// Get a cross-chain pledge by ID
fn get_cross_chain_pledge(env: Env, pledge_id: u64) -> CrossChainPledge;

/// Mark a backer's reward as fulfilled. Merchant-only.
fn fulfill_backer_reward(
/// Get a cross-chain pledge by source chain and source pledge ID
fn get_cross_chain_pledge_by_source(
env: Env,
merchant: Address,
campaign_id: u64,
backer: Address,
);

fn is_backer_reward_fulfilled(env: Env, campaign_id: u64, backer: Address) -> bool;

/// Claim a perk from the backer's selected tier after fulfillment.
fn claim_backer_perk(env: Env, backer: Address, campaign_id: u64, perk_index: u32);
source_chain: String,
source_pledge_id: u64,
) -> CrossChainPledge;

fn is_backer_perk_claimed(
env: Env,
campaign_id: u64,
backer: Address,
perk_index: u32,
) -> bool;
/// Get all cross-chain pledges
fn get_all_cross_chain_pledges(env: Env) -> Vec<CrossChainPledge>;
}
3 changes: 2 additions & 1 deletion contracts/shade/src/shade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
upgrade as upgrade_component, history as history_component,
invoice as invoice_component, merchant as merchant_component, pausable as pausable_component,
subscription as subscription_component, upgrade as upgrade_component,
history as history_component, escrow as escrow_component,
history as history_component, cross_chain_pledge as cross_chain_pledge_component,
escrow as escrow_component,
};
use crate::errors::ContractError;
use crate::events;
Expand Down
30 changes: 30 additions & 0 deletions contracts/shade/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ pub enum DataKey {
// --- Global token analytics ---
TokenAnalytics(Address),
TokenVolume(Address),
// --- Cross-chain pledge system ---
CrossChainPledge(u64),
CrossChainPledgeCount,
PledgeIdBySourceChain(String, u64), // source_chain, source_pledge_id
// --- Multi-sig massive withdrawal ---
/// Threshold (in token base units) above which a withdrawal requires multi-sig approval.
MultiSigThreshold(Address),
Expand Down Expand Up @@ -239,6 +243,32 @@ pub struct MerchantAnalyticsSummary {
pub last_updated: u64,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CrossChainPledgeStatus {
Pending,
Completed,
Failed,
Refunded,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CrossChainPledge {
pub id: u64,
pub source_chain: String,
pub source_pledge_id: u64,
pub destination_chain: String,
pub merchant: Address,
pub payer: Address,
pub token: Address,
pub amount: i128,
pub status: CrossChainPledgeStatus,
pub created_at: u64,
pub updated_at: u64,
pub memo: Option<String>,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CrossChainBridgePayload {
Expand Down