@@ -16,8 +16,6 @@ use types::*;
1616
1717mod zk_commitment;
1818
19- // FIXME: test.rs has compilation errors from merge conflict - re-enable after fix
20- // FIXME: test.rs has pre-existing compilation errors from a merge conflict - fix before enabling
2119#[ cfg( test) ]
2220mod test;
2321
@@ -1866,8 +1864,24 @@ impl IpRegistry {
18661864 ) -> bool {
18671865 let record = require_ip_exists ( & env, ip_id) ;
18681866
1869- // Reject if expired
1870- // Expiry check removed - field not in types
1867+ // Emit EXPIRY_TOPIC exactly once per expiry transition so off-chain
1868+ // indexers can cheaply detect an IP crossing into its grace period.
1869+ if record. expiry_timestamp != 0 && env. ledger ( ) . timestamp ( ) >= record. expiry_timestamp {
1870+ let already_notified: bool = env
1871+ . storage ( )
1872+ . persistent ( )
1873+ . get ( & DataKey :: ExpiryNotified ( ip_id) )
1874+ . unwrap_or ( false ) ;
1875+ if !already_notified {
1876+ env. events ( ) . publish (
1877+ ( EXPIRY_TOPIC , ip_id) ,
1878+ ( record. owner . clone ( ) , record. expiry_timestamp ) ,
1879+ ) ;
1880+ env. storage ( )
1881+ . persistent ( )
1882+ . set ( & DataKey :: ExpiryNotified ( ip_id) , & true ) ;
1883+ }
1884+ }
18711885
18721886 // Concatenate secret || blinding_factor into Bytes, then SHA256
18731887 let mut preimage = soroban_sdk:: Bytes :: new ( & env) ;
@@ -2236,9 +2250,12 @@ impl IpRegistry {
22362250 . get ( & DataKey :: SuggestedPrice ( ip_id) )
22372251 }
22382252
2239- /// Add a co-owner to an IP. Owner-only.
2253+ /// Add a co-owner to an IP with an explicit ownership percentage . Owner-only.
22402254 /// Co-owners can verify commitments but cannot transfer or revoke the IP.
2241- pub fn add_co_owner ( env : Env , ip_id : u64 , co_owner : Address ) {
2255+ ///
2256+ /// `percentage` is deducted from the current owner's remaining share, so the
2257+ /// full cap table (owner + co-owners) always sums to exactly 100.
2258+ pub fn add_co_owner ( env : Env , ip_id : u64 , co_owner : Address , percentage : u32 ) {
22422259 let mut record = require_ip_exists ( & env, ip_id) ;
22432260 record. owner . require_auth ( ) ;
22442261
@@ -2249,19 +2266,63 @@ impl IpRegistry {
22492266 }
22502267 }
22512268
2269+ let mut shares: Vec < OwnershipShare > = env
2270+ . storage ( )
2271+ . persistent ( )
2272+ . get ( & DataKey :: OwnershipShares ( ip_id) )
2273+ . unwrap_or_else ( || {
2274+ let mut v = Vec :: new ( & env) ;
2275+ v. push_back ( OwnershipShare {
2276+ address : record. owner . clone ( ) ,
2277+ percentage : 100 ,
2278+ } ) ;
2279+ v
2280+ } ) ;
2281+
2282+ let owner_idx = shares
2283+ . iter ( )
2284+ . position ( |s| s. address == record. owner )
2285+ . unwrap_or_else ( || {
2286+ env. panic_with_error ( Error :: from_contract_error (
2287+ ContractError :: InvalidShareTotal as u32 ,
2288+ ) )
2289+ } ) as u32 ;
2290+ let mut owner_share = shares. get ( owner_idx) . unwrap ( ) ;
2291+ if percentage == 0 || percentage > owner_share. percentage {
2292+ env. panic_with_error ( Error :: from_contract_error (
2293+ ContractError :: InvalidOwnershipPercentage as u32 ,
2294+ ) ) ;
2295+ }
2296+ owner_share. percentage -= percentage;
2297+ shares. set ( owner_idx, owner_share) ;
2298+ shares. push_back ( OwnershipShare {
2299+ address : co_owner. clone ( ) ,
2300+ percentage,
2301+ } ) ;
2302+ require_valid_share_total ( & env, & shares) ;
2303+
22522304 record. co_owners . push_back ( co_owner. clone ( ) ) ;
22532305 env. storage ( )
22542306 . persistent ( )
22552307 . set ( & DataKey :: IpRecord ( ip_id) , & record) ;
22562308 env. storage ( )
22572309 . persistent ( )
22582310 . extend_ttl ( & DataKey :: IpRecord ( ip_id) , LEDGER_BUMP , LEDGER_BUMP ) ;
2311+ env. storage ( )
2312+ . persistent ( )
2313+ . set ( & DataKey :: OwnershipShares ( ip_id) , & shares) ;
2314+ env. storage ( ) . persistent ( ) . extend_ttl (
2315+ & DataKey :: OwnershipShares ( ip_id) ,
2316+ LEDGER_BUMP ,
2317+ LEDGER_BUMP ,
2318+ ) ;
22592319
22602320 env. events ( )
22612321 . publish ( ( symbol_short ! ( "co_add" ) , record. owner ) , ( ip_id, co_owner) ) ;
22622322 }
22632323
22642324 /// Remove a co-owner from an IP. Owner-only.
2325+ /// The removed co-owner's percentage is returned to the primary owner's share.
22652326 pub fn remove_co_owner ( env : Env , ip_id : u64 , co_owner : Address ) {
22662327 let mut record = require_ip_exists ( & env, ip_id) ;
22672328 record. owner . require_auth ( ) ;
@@ -2278,11 +2339,47 @@ impl IpRegistry {
22782339 LEDGER_BUMP ,
22792340 ) ;
22802341
2342+ if let Some ( mut shares) = env
2343+ . storage ( )
2344+ . persistent ( )
2345+ . get :: < DataKey , Vec < OwnershipShare > > ( & DataKey :: OwnershipShares ( ip_id) )
2346+ {
2347+ if let Some ( share_idx) = shares. iter ( ) . position ( |s| s. address == co_owner) {
2348+ let removed = shares. get ( share_idx as u32 ) . unwrap ( ) ;
2349+ shares. remove ( share_idx as u32 ) ;
2350+ if let Some ( owner_idx) = shares. iter ( ) . position ( |s| s. address == record. owner )
2351+ {
2352+ let mut owner_share = shares. get ( owner_idx as u32 ) . unwrap ( ) ;
2353+ owner_share. percentage += removed. percentage ;
2354+ shares. set ( owner_idx as u32 , owner_share) ;
2355+ }
2356+ require_valid_share_total ( & env, & shares) ;
2357+ env. storage ( )
2358+ . persistent ( )
2359+ . set ( & DataKey :: OwnershipShares ( ip_id) , & shares) ;
2360+ env. storage ( ) . persistent ( ) . extend_ttl (
2361+ & DataKey :: OwnershipShares ( ip_id) ,
2362+ LEDGER_BUMP ,
2363+ LEDGER_BUMP ,
2364+ ) ;
2365+ }
2366+ }
2367+
22812368 env. events ( )
22822369 . publish ( ( symbol_short ! ( "co_rem" ) , record. owner ) , ( ip_id, co_owner) ) ;
22832370 }
22842371 }
22852372
2373+ /// Get the current ownership cap table (owner + co-owners) for an IP.
2374+ /// Returns an empty vector if no co-owners have ever been added.
2375+ pub fn get_ownership_shares ( env : Env , ip_id : u64 ) -> Vec < OwnershipShare > {
2376+ require_ip_exists ( & env, ip_id) ;
2377+ env. storage ( )
2378+ . persistent ( )
2379+ . get ( & DataKey :: OwnershipShares ( ip_id) )
2380+ . unwrap_or_else ( || Vec :: new ( & env) )
2381+ }
2382+
22862383 /// Create a new version of an existing IP commitment.
22872384 ///
22882385 /// This function allows an IP owner to create a new version of their IP
@@ -5515,6 +5612,9 @@ impl IpRegistry {
55155612 env. storage ( )
55165613 . persistent ( )
55175614 . extend_ttl ( & DataKey :: IpRecord ( ip_id) , LEDGER_BUMP , LEDGER_BUMP ) ;
5615+ env. storage ( )
5616+ . persistent ( )
5617+ . remove ( & DataKey :: ExpiryNotified ( ip_id) ) ;
55185618 }
55195619
55205620 /// Renew an IP commitment's expiry. Owner-only.
@@ -5537,6 +5637,9 @@ impl IpRegistry {
55375637 env. storage ( )
55385638 . persistent ( )
55395639 . extend_ttl ( & DataKey :: IpRecord ( ip_id) , LEDGER_BUMP , LEDGER_BUMP ) ;
5640+ env. storage ( )
5641+ . persistent ( )
5642+ . remove ( & DataKey :: ExpiryNotified ( ip_id) ) ;
55405643
55415644 env. events ( ) . publish (
55425645 ( symbol_short ! ( "ip_renew" ) , record. owner ) ,
0 commit comments