Skip to content

Commit 19d6e74

Browse files
Merge pull request #2660 from aliraza556/feature/bounty-stake-management
Implement: Add Bounty Stake Management System
2 parents 8201a5c + 80e6605 commit 19d6e74

File tree

8 files changed

+873
-1
lines changed

8 files changed

+873
-1
lines changed

db/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func InitDB() {
106106
db.AutoMigrate(&SkillInstall{})
107107
db.AutoMigrate(&SSEMessageLog{})
108108
db.AutoMigrate(&CodeSpaceMap{})
109+
db.AutoMigrate(&BountyStake{})
109110

110111
DB.MigrateTablesWithOrgUuid()
111112
DB.MigrateOrganizationToWorkspace()

db/db.go

+155
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/lib/pq"
2020
_ "github.com/lib/pq"
2121
"github.com/rs/xid"
22+
"gorm.io/gorm"
2223

2324
"github.com/stakwork/sphinx-tribes/auth"
2425
"github.com/stakwork/sphinx-tribes/utils"
@@ -2202,3 +2203,157 @@ func (db database) GetBountiesByWorkspaceAndTimeRange(workspaceId string, startD
22022203

22032204
return bounties, err
22042205
}
2206+
2207+
func (db database) CreateBountyStake(stake BountyStake) (*BountyStake, error) {
2208+
if stake.BountyID == 0 {
2209+
return nil, errors.New("bounty ID is required")
2210+
}
2211+
2212+
if stake.HunterPubKey == "" {
2213+
return nil, errors.New("hunter public key is required")
2214+
}
2215+
2216+
if stake.Amount <= 0 {
2217+
return nil, errors.New("stake amount must be greater than zero")
2218+
}
2219+
2220+
bounty := db.GetBounty(stake.BountyID)
2221+
if bounty.ID == 0 {
2222+
return nil, errors.New("bounty not found")
2223+
}
2224+
2225+
if !bounty.IsStakable {
2226+
return nil, errors.New("bounty is not stakable")
2227+
}
2228+
2229+
if stake.Amount < bounty.StakeMin {
2230+
return nil, fmt.Errorf("stake amount must be at least %d", bounty.StakeMin)
2231+
}
2232+
2233+
if bounty.CurrentStakers >= bounty.MaxStakers {
2234+
return nil, errors.New("maximum number of stakers reached for this bounty")
2235+
}
2236+
2237+
if stake.Status == "" {
2238+
stake.Status = StakeStatusNew
2239+
}
2240+
2241+
if err := db.db.Create(&stake).Error; err != nil {
2242+
return nil, fmt.Errorf("failed to create bounty stake: %w", err)
2243+
}
2244+
2245+
if err := db.db.Model(&NewBounty{}).Where("id = ?", stake.BountyID).
2246+
Update("current_stakers", gorm.Expr("current_stakers + 1")).Error; err != nil {
2247+
return nil, fmt.Errorf("failed to update bounty stakers count: %w", err)
2248+
}
2249+
2250+
return &stake, nil
2251+
}
2252+
2253+
func (db database) GetAllBountyStakes() ([]BountyStake, error) {
2254+
var stakes []BountyStake
2255+
if err := db.db.Find(&stakes).Error; err != nil {
2256+
return nil, fmt.Errorf("failed to retrieve bounty stakes: %w", err)
2257+
}
2258+
return stakes, nil
2259+
}
2260+
2261+
func (db database) GetBountyStakesByBountyID(bountyID uint) ([]BountyStake, error) {
2262+
var stakes []BountyStake
2263+
if err := db.db.Where("bounty_id = ?", bountyID).Find(&stakes).Error; err != nil {
2264+
return nil, fmt.Errorf("failed to retrieve stakes for bounty %d: %w", bountyID, err)
2265+
}
2266+
return stakes, nil
2267+
}
2268+
2269+
func (db database) GetBountyStakeByID(stakeID uuid.UUID) (*BountyStake, error) {
2270+
var stake BountyStake
2271+
if err := db.db.Where("id = ?", stakeID).First(&stake).Error; err != nil {
2272+
if errors.Is(err, gorm.ErrRecordNotFound) {
2273+
return nil, fmt.Errorf("stake with ID %s not found", stakeID)
2274+
}
2275+
return nil, fmt.Errorf("failed to retrieve stake with ID %s: %w", stakeID, err)
2276+
}
2277+
return &stake, nil
2278+
}
2279+
2280+
func (db database) GetBountyStakesByHunterPubKey(hunterPubKey string) ([]BountyStake, error) {
2281+
var stakes []BountyStake
2282+
if err := db.db.Where("hunter_pub_key = ?", hunterPubKey).Find(&stakes).Error; err != nil {
2283+
return nil, fmt.Errorf("failed to retrieve stakes for hunter %s: %w", hunterPubKey, err)
2284+
}
2285+
return stakes, nil
2286+
}
2287+
2288+
func (db database) UpdateBountyStake(stakeID uuid.UUID, updates map[string]interface{}) (*BountyStake, error) {
2289+
2290+
stake, err := db.GetBountyStakeByID(stakeID)
2291+
if err != nil {
2292+
return nil, err
2293+
}
2294+
2295+
if statusVal, ok := updates["status"]; ok {
2296+
status, ok := statusVal.(StakeStatus)
2297+
if !ok {
2298+
statusStr, ok := statusVal.(string)
2299+
if ok {
2300+
status = StakeStatus(statusStr)
2301+
} else {
2302+
return nil, errors.New("invalid status type")
2303+
}
2304+
}
2305+
2306+
if status == StakeStatusActive && stake.StakedAt == nil {
2307+
now := time.Now()
2308+
updates["staked_at"] = now
2309+
}
2310+
2311+
if status == StakeStatusReturned && stake.ReturnedAt == nil {
2312+
now := time.Now()
2313+
updates["returned_at"] = now
2314+
2315+
if err := db.db.Model(&NewBounty{}).Where("id = ?", stake.BountyID).
2316+
Update("current_stakers", gorm.Expr("current_stakers - 1")).Error; err != nil {
2317+
return nil, fmt.Errorf("failed to update bounty stakers count: %w", err)
2318+
}
2319+
}
2320+
}
2321+
2322+
if err := db.db.Model(&BountyStake{}).Where("id = ?", stakeID).Updates(updates).Error; err != nil {
2323+
return nil, fmt.Errorf("failed to update stake with ID %s: %w", stakeID, err)
2324+
}
2325+
2326+
return db.GetBountyStakeByID(stakeID)
2327+
}
2328+
2329+
func (db database) DeleteBountyStake(stakeID uuid.UUID) error {
2330+
2331+
stake, err := db.GetBountyStakeByID(stakeID)
2332+
if err != nil {
2333+
return err
2334+
}
2335+
2336+
tx := db.db.Begin()
2337+
if tx.Error != nil {
2338+
return fmt.Errorf("failed to begin transaction: %w", tx.Error)
2339+
}
2340+
2341+
if err := tx.Delete(&BountyStake{}, "id = ?", stakeID).Error; err != nil {
2342+
tx.Rollback()
2343+
return fmt.Errorf("failed to delete stake with ID %s: %w", stakeID, err)
2344+
}
2345+
2346+
if stake.StakedAt != nil && stake.ReturnedAt == nil {
2347+
if err := tx.Model(&NewBounty{}).Where("id = ?", stake.BountyID).
2348+
Update("current_stakers", gorm.Expr("current_stakers - 1")).Error; err != nil {
2349+
tx.Rollback()
2350+
return fmt.Errorf("failed to update bounty stakers count: %w", err)
2351+
}
2352+
}
2353+
2354+
if err := tx.Commit().Error; err != nil {
2355+
return fmt.Errorf("failed to commit transaction: %w", err)
2356+
}
2357+
2358+
return nil
2359+
}

db/interface.go

+7
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,11 @@ type Database interface {
350350
GetCodeSpaceMapByID(id uuid.UUID) (CodeSpaceMap, error)
351351
UpdateCodeSpaceMap(id uuid.UUID, updates map[string]interface{}) (CodeSpaceMap, error)
352352
DeleteCodeSpaceMap(id uuid.UUID) error
353+
CreateBountyStake(stake BountyStake) (*BountyStake, error)
354+
GetAllBountyStakes() ([]BountyStake, error)
355+
GetBountyStakesByBountyID(bountyID uint) ([]BountyStake, error)
356+
GetBountyStakeByID(stakeID uuid.UUID) (*BountyStake, error)
357+
GetBountyStakesByHunterPubKey(hunterPubKey string) ([]BountyStake, error)
358+
UpdateBountyStake(stakeID uuid.UUID, updates map[string]interface{}) (*BountyStake, error)
359+
DeleteBountyStake(stakeID uuid.UUID) error
353360
}

db/structs.go

+37
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,11 @@ type Bounty struct {
415415
PaymentFailed bool `gorm:"default:false" json:"payment_failed"`
416416
AccessRestriction *AccessRestrictionType `gorm:"type:varchar(20);default:null" json:"access_restriction,omitempty"`
417417
UnlockCode *string `gorm:"type:varchar(6);default:null;index" json:"unlock_code,omitempty"`
418+
IsStakable bool `gorm:"default:false" json:"is_stakable"`
419+
StakeMin int64 `gorm:"default:0" json:"stake_min"`
420+
MaxStakers int `gorm:"default:1" json:"max_stakers"`
421+
CurrentStakers int `gorm:"default:0" json:"current_stakers"`
422+
Stakes []BountyStake `gorm:"foreignKey:BountyID" json:"stakes,omitempty"`
418423
}
419424

420425
// Todo: Change back to Bounty
@@ -458,6 +463,11 @@ type NewBounty struct {
458463
ProofOfWorkCount int `gorm:"type:integer;default:0;not null" json:"pow"`
459464
AccessRestriction *AccessRestrictionType `gorm:"type:varchar(20);default:null" json:"access_restriction,omitempty"`
460465
UnlockCode *string `gorm:"type:varchar(6);default:null;index" json:"unlock_code,omitempty"`
466+
IsStakable bool `gorm:"default:false" json:"is_stakable"`
467+
StakeMin int64 `gorm:"default:0" json:"stake_min"`
468+
MaxStakers int `gorm:"default:1" json:"max_stakers"`
469+
CurrentStakers int `gorm:"default:0" json:"current_stakers"`
470+
Stakes []BountyStake `gorm:"foreignKey:BountyID" json:"stakes,omitempty"`
461471
}
462472

463473
type BountyOwners struct {
@@ -1572,6 +1582,33 @@ type CodeSpaceMap struct {
15721582
UserPubkey string `json:"userPubkey" gorm:"index"`
15731583
}
15741584

1585+
type StakeStatus string
1586+
1587+
const (
1588+
StakeStatusNew StakeStatus = "NEW"
1589+
StakeStatusPending StakeStatus = "PENDING"
1590+
StakeStatusActive StakeStatus = "ACTIVE"
1591+
StakeStatusCompleted StakeStatus = "COMPLETED"
1592+
StakeStatusReturned StakeStatus = "RETURNED"
1593+
StakeStatusFailed StakeStatus = "FAILED"
1594+
)
1595+
1596+
type BountyStake struct {
1597+
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
1598+
BountyID uint `json:"bounty_id" gorm:"index;not null"`
1599+
HunterPubKey string `json:"hunter_pubkey" gorm:"type:varchar(255);not null"`
1600+
Amount int64 `json:"amount" gorm:"not null"`
1601+
Status StakeStatus `json:"status" gorm:"type:varchar(20);default:'NEW'"`
1602+
Invoice string `json:"invoice" gorm:"type:text"`
1603+
StakeReceipt string `json:"stake_receipt" gorm:"type:text"`
1604+
StakeReturn string `json:"stake_return" gorm:"type:text"`
1605+
Note string `json:"note" gorm:"type:text"`
1606+
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
1607+
StakedAt *time.Time `json:"staked_at"`
1608+
ReturnedAt *time.Time `json:"returned_at"`
1609+
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
1610+
}
1611+
15751612
func (Person) TableName() string {
15761613
return "people"
15771614
}

db/test_config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func InitTestDB() {
9191
db.AutoMigrate(&SkillInstall{})
9292
db.AutoMigrate(&SSEMessageLog{})
9393
db.AutoMigrate(&CodeSpaceMap{})
94-
94+
db.AutoMigrate(&BountyStake{})
9595
people := TestDB.GetAllPeople()
9696
for _, p := range people {
9797
if p.Uuid == "" {

0 commit comments

Comments
 (0)