PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoswap/launchpad/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
gnoswap / launchpad
Files
23 (README)(gnomod.toml)
Exported functions
1
Module
gno.land/r/gnoswap/launchpad/v1
gno
0.9

Files (23)

  • README.mdmarkdown
  • gnomod.tomltoml
  • assert.gnogno
  • consts.gnogno
  • deposit.gnogno
  • emits.gnogno
  • errors.gnogno
  • getter_utils.gnogno
  • getter.gnogno
  • init.gnogno
  • instance.gnogno
  • launchpad_deposit.gnogno
  • launchpad_project.gnogno
  • launchpad_protocol_fee.gnogno
  • launchpad_reward.gnogno
  • launchpad_withdraw.gnogno
  • project_tier.gnogno
  • project.gnogno
  • render.gnogno
  • reward_manager.gnogno
  • reward_state.gnogno
  • state.gnogno
  • utils.gnogno
  • launchpad_project.gnogno
    1package launchpad23import (4	"chain"5	"chain/runtime"6	"errors"7	"strconv"8

    Functions

    • NewLaunchpadV1(launchpadStore interface {GetDepositCounter func() *gno.land/r/gnoswap/launchpad.Counter; GetDeposits func() *gno.land/p/nt/bptree/v0.BPTree; GetProjectRecipients func() *gno.land/p/nt/bptree/v0.BPTree; GetProjectTierRewardManagers func() *gno.land/p/nt/bptree/v0.BPTree; GetProjects func() *gno.land/p/nt/bptree/v0.BPTree; GetTotalGNSStakedAmount func() int64; HasDepositCounterStoreKey func() bool; HasDepositsKey func() bool; HasProjectRecipientsKey func() bool; HasProjectTierRewardManagersKey func() bool; HasProjectsKey func() bool; HasTotalGNSStakedAmountKey func() bool; NextDepositID func() string; SetDepositCounter func(int, .uverse.realm, *gno.land/r/gnoswap/launchpad.Counter) .uverse.error; SetDeposits func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetProjectRecipients func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetProjectTierRewardManagers func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetProjects func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetTotalGNSStakedAmount func(int, .uverse.realm, int64) .uverse.error}) interface {CollectDepositGns func(int, .uverse.realm, string) (int64, .uverse.error); CollectEmissionReward func(int, .uverse.realm); CollectProtocolFee func(int, .uverse.realm); CollectProtocolFeeReward func(int, .uverse.realm, string); CollectRewardByDepositId func(int, .uverse.realm, string) int64; CreateProject func(int, .uverse.realm, string, string, .uverse.address, int64, string, string, int64, int64, int64, int64) string; DepositGns func(int, .uverse.realm, string, int64, string) string; GetCurrentDepositId func() int64; GetDeposit func(string) (gno.land/r/gnoswap/launchpad.Deposit, .uverse.error); GetDepositAmount func(string) (int64, .uverse.error); GetDepositCount func() int; GetDepositCreatedAt func(string) (int64, .uverse.error); GetDepositCreatedHeight func(string) (int64, .uverse.error); GetDepositEndTime func(string) (int64, .uverse.error); GetDepositProjectID func(string) (string, .uverse.error); GetDepositProjectTierID func(string) (string, .uverse.error); GetDepositTier func(string) (int64, .uverse.error); GetDepositWithdrawnHeight func(string) (int64, .uverse.error); GetDepositWithdrawnTime func(string) (int64, .uverse.error); GetProjectActiveStatus func(string) (bool, .uverse.error); GetProjectCondition func(string, string) (*gno.land/r/gnoswap/launchpad.ProjectCondition, .uverse.error); GetProjectCreatedAt func(string) (int64, .uverse.error); GetProjectCreatedHeight func(string) (int64, .uverse.error); GetProjectDepositAmount func(string) (int64, .uverse.error); GetProjectName func(string) (string, .uverse.error); GetProjectRecipient func(string) (.uverse.address, .uverse.error); GetProjectTier func(string, int64) (*gno.land/r/gnoswap/launchpad.ProjectTier, .uverse.error); GetProjectTierDistributeAmountPerSecondX128 func(string, int64) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetProjectTierEndTime func(string, int64) (int64, .uverse.error); GetProjectTierRewardAccumulatedDistributeAmount func(string) (int64, .uverse.error); GetProjectTierRewardAccumulatedRewardPerDepositX128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetProjectTierRewardAccumulatedTime func(string) (int64, .uverse.error); GetProjectTierRewardClaimableDuration func(string) (int64, .uverse.error); GetProjectTierRewardDistributeAmountPerSecondX128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetProjectTierRewardDistributeEndTime func(string) (int64, .uverse.error); GetProjectTierRewardDistributeStartTime func(string) (int64, .uverse.error); GetProjectTierRewardManager func(string) (*gno.land/r/gnoswap/launchpad.RewardManager, .uverse.error); GetProjectTierRewardManagerCount func() int; GetProjectTierRewardTotalClaimedAmount func(string) (int64, .uverse.error); GetProjectTierRewardTotalDistributeAmount func(string) (int64, .uverse.error); GetProjectTierRewards func(string, int64) *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetProjectTierStartTime func(string, int64) (int64, .uverse.error); GetProjectTierTotalCollectedAmount func(string, int64) (int64, .uverse.error); GetProjectTierTotalDepositAmount func(string, int64) (int64, .uverse.error); GetProjectTierTotalDepositCount func(string, int64) (int64, .uverse.error); GetProjectTierTotalDistributeAmount func(string, int64) (int64, .uverse.error); GetProjectTierTotalWithdrawAmount func(string, int64) (int64, .uverse.error); GetProjectTierTotalWithdrawCount func(string, int64) (int64, .uverse.error); GetProjectTiersRatios func(string) (map[int64]int64, .uverse.error); GetProjectTokenPath func(string) (string, .uverse.error); GetProjects func() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetRewardState func(string, string) (*gno.land/r/gnoswap/launchpad.RewardState, .uverse.error); GetTotalGNSStakedAmount func() int64; Render func(string) string; TransferLeftFromProjectByAdmin func(int, .uverse.realm, string, .uverse.address) int64}

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    Rendered

    RenderedRawgnoweb ↗
    This realm does not provide renderable output (vm/qrender → NoRenderDeclError). View on gnoweb ↗

    vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.

    "strings"
    9 "time"
    10
    11 gnsmath "gno.land/p/gnoswap/gnsmath/v1"
    12 "gno.land/p/gnoswap/utils/v1"
    13 ufmt "gno.land/p/nt/ufmt/v0"
    14
    15 "gno.land/r/gnoswap/access/v1"
    16 "gno.land/r/gnoswap/common"
    17 "gno.land/r/gnoswap/emission"
    18 "gno.land/r/gnoswap/halt/v1"
    19 "gno.land/r/gnoswap/launchpad"
    20)
    21
    22// CreateProject creates a new launchpad project with tiered allocations.
    23//
    24// Parameters:
    25// - _: Noncrossing implementation-call discriminator; pass 0.
    26// - rlm: Current realm context forwarded unchanged by the launchpad proxy.
    27// - name: project name, limited by the project validation rules.
    28// - tokenPath: reward token contract path whose balance is deposited into the project.
    29// - recipient: project recipient address recorded for authorization and later collection.
    30// - depositAmount: amount of reward tokens to deposit into the project.
    31// - conditionTokens: `*PAD*`-separated token paths for eligibility conditions.
    32// - conditionAmounts: `*PAD*`-separated minimum balances aligned with conditionTokens.
    33// - tier30Ratio: percentage of the allocation assigned to the 30-day tier.
    34// - tier90Ratio: percentage of the allocation assigned to the 90-day tier.
    35// - tier180Ratio: percentage of the allocation assigned to the 180-day tier.
    36// - startTime: project start timestamp in Unix seconds, subject to the minimum delay.
    37//
    38// Returns:
    39// - projectID: newly created launchpad project identifier.
    40//
    41// Only callable by admin or governance.
    42func (lp *launchpadV1) CreateProject(
    43 _ int,
    44 rlm realm,
    45 name string,
    46 tokenPath string,
    47 recipient address,
    48 depositAmount int64,
    49 conditionTokens string,
    50 conditionAmounts string,
    51 tier30Ratio int64,
    52 tier90Ratio int64,
    53 tier180Ratio int64,
    54 startTime int64,
    55) string {
    56 access.AssertIsRlmCurrent(0, rlm)
    57
    58 halt.AssertIsNotHaltedLaunchpad()
    59
    60 previousRealm := rlm.Previous()
    61 caller := previousRealm.Address()
    62 access.AssertIsAdminOrGovernance(caller)
    63
    64 launchpadAddr := rlm.Address()
    65 currentHeight := runtime.ChainHeight()
    66 currentTime := time.Now().Unix()
    67
    68 params := &createProjectParams{
    69 name: name,
    70 tokenPath: tokenPath,
    71 recipient: recipient,
    72 depositAmount: depositAmount,
    73 conditionTokens: conditionTokens,
    74 conditionAmounts: conditionAmounts,
    75 tier30Ratio: tier30Ratio,
    76 tier90Ratio: tier90Ratio,
    77 tier180Ratio: tier180Ratio,
    78 startTime: startTime,
    79 currentTime: currentTime,
    80 currentHeight: currentHeight,
    81 minimumStartDelayTime: projectMinimumStartDelayTime,
    82 }
    83
    84 // Checks: validate balance before creating project
    85 tokenBalance := common.BalanceOf(tokenPath, caller)
    86 if tokenBalance < depositAmount {
    87 panic(
    88 makeErrorWithDetails(
    89 errInsufficientBalance, ufmt.Sprintf(
    90 "caller(%s) balance(%d) < depositAmount(%d)",
    91 caller.String(), tokenBalance, depositAmount,
    92 ),
    93 ),
    94 )
    95 }
    96
    97 // Effects: create project and save state
    98 project, err := lp.createProject(0, rlm, params)
    99 if err != nil {
    100 panic(err)
    101 }
    102
    103 // Interactions: transfer tokens
    104 common.SafeGRC20TransferFrom(
    105 0, rlm,
    106 tokenPath,
    107 caller,
    108 launchpadAddr,
    109 depositAmount,
    110 )
    111
    112 tier30, err := getProjectTier(project, projectTier30)
    113 if err != nil {
    114 panic(err)
    115 }
    116
    117 tier90, err := getProjectTier(project, projectTier90)
    118 if err != nil {
    119 panic(err)
    120 }
    121
    122 tier180, err := getProjectTier(project, projectTier180)
    123 if err != nil {
    124 panic(err)
    125 }
    126
    127 conditionEventAttrs := buildConditionEventAttrs(params.conditionTokens, params.conditionAmounts)
    128
    129 eventAttrs := append([]string{
    130 "prevAddr", caller.String(),
    131 "prevRealm", previousRealm.PkgPath(),
    132 "name", name,
    133 "tokenPath", tokenPath,
    134 "recipient", recipient.String(),
    135 "depositAmount", utils.FormatInt(depositAmount),
    136 "tier30Ratio", utils.FormatInt(params.tier30Ratio),
    137 "tier90Ratio", utils.FormatInt(params.tier90Ratio),
    138 "tier180Ratio", utils.FormatInt(params.tier180Ratio),
    139 "startTime", utils.FormatInt(params.startTime),
    140 "projectId", project.ID(),
    141 "tier30Amount", utils.FormatInt(tier30.TotalDistributeAmount()),
    142 "tier30EndTime", utils.FormatInt(tier30.EndTime()),
    143 "tier90Amount", utils.FormatInt(tier90.TotalDistributeAmount()),
    144 "tier90EndTime", utils.FormatInt(tier90.EndTime()),
    145 "tier180Amount", utils.FormatInt(tier180.TotalDistributeAmount()),
    146 "tier180EndTime", utils.FormatInt(tier180.EndTime()),
    147 }, conditionEventAttrs...)
    148
    149 chain.Emit(
    150 "CreateProject",
    151 eventAttrs...,
    152 )
    153
    154 return project.ID()
    155}
    156
    157// createProject creates a new project with the given parameters.
    158// This function validates the input parameters, creates the project structure,
    159// and sets up the project tiers and reward managers.
    160// Returns the created project and any error.
    161func (lp *launchpadV1) createProject(_ int, rlm realm, params *createProjectParams) (*launchpad.Project, error) {
    162 if err := params.validate(); err != nil {
    163 return nil, err
    164 }
    165
    166 // create project
    167 project := launchpad.NewProject(
    168 params.name,
    169 params.tokenPath,
    170 params.depositAmount,
    171 params.recipient,
    172 params.currentHeight,
    173 params.currentTime,
    174 )
    175
    176 // Get state components
    177 projects := lp.store.GetProjects()
    178 projectTierRewardManagers := lp.store.GetProjectTierRewardManagers()
    179
    180 // check duplicate project
    181 if projects.Has(project.ID()) {
    182 return nil, makeErrorWithDetails(
    183 errDuplicateProject,
    184 ufmt.Sprintf("project(%s) already exists", project.ID()),
    185 )
    186 }
    187
    188 projectConditions, err := launchpad.NewProjectConditionsWithError(params.conditionTokens, params.conditionAmounts)
    189 if err != nil {
    190 return nil, err
    191 }
    192
    193 for _, condition := range projectConditions {
    194 addProjectCondition(project, condition.TokenPath(), condition)
    195 }
    196
    197 projectTierRatios := map[int64]int64{
    198 projectTier30: params.tier30Ratio,
    199 projectTier90: params.tier90Ratio,
    200 projectTier180: params.tier180Ratio,
    201 }
    202
    203 accumulatedTierDistributeAmount := int64(0)
    204
    205 for _, duration := range projectTierDurations {
    206 rewardCollectableDuration := projectTierRewardCollectableDuration[duration]
    207 tierDurationTime := projectTierDurationTimes[duration]
    208 tierDistributeAmount := gnsmath.SafeMulDivInt64(params.depositAmount, projectTierRatios[duration], 100)
    209 accumulatedTierDistributeAmount = gnsmath.SafeAddInt64(accumulatedTierDistributeAmount, tierDistributeAmount)
    210
    211 // if the last tier, distribute the remaining amount
    212 if duration == projectTier180 {
    213 remainTierDistributeAmount := gnsmath.SafeSubInt64(params.depositAmount, accumulatedTierDistributeAmount)
    214 tierDistributeAmount = gnsmath.SafeAddInt64(tierDistributeAmount, remainTierDistributeAmount)
    215 }
    216
    217 projectTier := newProjectTier(
    218 project.ID(),
    219 duration,
    220 tierDistributeAmount,
    221 params.startTime,
    222 params.startTime+tierDurationTime,
    223 )
    224 addProjectTier(project, duration, projectTier)
    225
    226 projectTierRewardManagers.Set(projectTier.ID(), newRewardManager(
    227 projectTier.TotalDistributeAmount(),
    228 projectTier.StartTime(),
    229 projectTier.EndTime(),
    230 rewardCollectableDuration,
    231 ))
    232 }
    233
    234 project.SetTiersRatios(projectTierRatios)
    235 projects.Set(project.ID(), project)
    236
    237 // Save the modified state back
    238 if err := lp.store.SetProjects(0, rlm, projects); err != nil {
    239 return nil, err
    240 }
    241 if err := lp.store.SetProjectTierRewardManagers(0, rlm, projectTierRewardManagers); err != nil {
    242 return nil, err
    243 }
    244 recipients := lp.store.GetProjectRecipients()
    245 recipients.Set(project.Recipient().String(), true)
    246 if err := lp.store.SetProjectRecipients(0, rlm, recipients); err != nil {
    247 return nil, err
    248 }
    249
    250 return project, nil
    251}
    252
    253// TransferLeftFromProjectByAdmin transfers the remaining refundable project-token
    254// balance of an ended project to a specified recipient.
    255// Only admin can call this function. Amounts reserved for active depositor
    256// claims are excluded from the transfer.
    257//
    258// Parameters:
    259// - _: Noncrossing implementation-call discriminator; pass 0.
    260// - rlm: Current realm context forwarded unchanged by the launchpad proxy.
    261// - projectID: ID of the ended project.
    262// - recipient: address that receives the remaining project-token balance.
    263//
    264// Returns:
    265// - amount: amount of project-token balance transferred to the recipient.
    266func (lp *launchpadV1) TransferLeftFromProjectByAdmin(_ int, rlm realm, projectID string, recipient address) int64 {
    267 access.AssertIsRlmCurrent(0, rlm)
    268
    269 halt.AssertIsNotHaltedLaunchpad()
    270
    271 previousRealm := rlm.Previous()
    272 caller := previousRealm.Address()
    273 access.AssertIsAdmin(caller)
    274
    275 currentHeight := runtime.ChainHeight()
    276 currentTime := time.Now().Unix()
    277
    278 project, err := lp.getProject(projectID)
    279 if err != nil {
    280 panic(err)
    281 }
    282
    283 projectLeftReward, err := lp.transferLeftFromProject(0, rlm, project, recipient, currentTime)
    284 if err != nil {
    285 panic(err)
    286 }
    287
    288 tier30, err := getProjectTier(project, projectTier30)
    289 if err != nil {
    290 panic(err)
    291 }
    292
    293 tier90, err := getProjectTier(project, projectTier90)
    294 if err != nil {
    295 panic(err)
    296 }
    297
    298 tier180, err := getProjectTier(project, projectTier180)
    299 if err != nil {
    300 panic(err)
    301 }
    302
    303 chain.Emit(
    304 "TransferLeftFromProjectByAdmin",
    305 "prevAddr", caller.String(),
    306 "prevRealm", previousRealm.PkgPath(),
    307 "projectId", projectID,
    308 "recipient", recipient.String(),
    309 "tokenPath", project.TokenPath(),
    310 "leftReward", utils.FormatInt(projectLeftReward),
    311 "tier30Full", utils.FormatInt(tier30.TotalDepositAmount()),
    312 "tier30Left", utils.FormatInt(getCalculatedLeftReward(tier30)),
    313 "tier90Full", utils.FormatInt(tier90.TotalDepositAmount()),
    314 "tier90Left", utils.FormatInt(getCalculatedLeftReward(tier90)),
    315 "tier180Full", utils.FormatInt(tier180.TotalDepositAmount()),
    316 "tier180Left", utils.FormatInt(getCalculatedLeftReward(tier180)),
    317 "currentHeight", utils.FormatInt(currentHeight),
    318 "currentTime", utils.FormatInt(currentTime),
    319 )
    320
    321 return projectLeftReward
    322}
    323
    324// transferLeftFromProject transfers the remaining rewards of a project to a specified recipient.
    325// This function is called by an admin to transfer any unclaimed rewards from a project to a recipient address.
    326// It validates the project ID, checks the recipient conditions, calculates the remaining rewards, and performs the transfer.
    327// Returns the amount of rewards transferred to the recipient and any error.
    328func (lp *launchpadV1) transferLeftFromProject(_ int, rlm realm, project *launchpad.Project, recipient address, currentTime int64) (int64, error) {
    329 if err := validateRefundProject(project, recipient, currentTime); err != nil {
    330 return 0, err
    331 }
    332
    333 emission.MintAndDistributeGns(cross(rlm))
    334
    335 accumTotalDistributeAmount := int64(0)
    336 accumLeftReward := int64(0)
    337 accumCollectedReward := int64(0)
    338 accumCollectableReward := int64(0)
    339 refundableAmountsByTier := make(map[int64]int64)
    340
    341 for duration, tier := range project.Tiers() {
    342 // Settlement uses tier aggregate accounting, never the unbounded
    343 // per-deposit reward tree.
    344 tierCollectableReward := int64(0)
    345 if getTierCurrentDepositCount(tier) > 0 {
    346 var err error
    347 tierCollectableReward, err = lp.applyTierRewardGrowthWithRewards(tier, currentTime)
    348 if err != nil {
    349 return 0, err
    350 }
    351
    352 accumCollectableReward = gnsmath.SafeAddInt64(accumCollectableReward, tierCollectableReward)
    353 }
    354
    355 leftReward := getCalculatedLeftReward(tier)
    356 tierRefundableAmount := gnsmath.SafeSubInt64(leftReward, tierCollectableReward)
    357 if tierRefundableAmount < 0 {
    358 return 0, errors.New(ufmt.Sprintf("tierRefundableAmount(%d) < 0", tierRefundableAmount))
    359 }
    360
    361 refundableAmountsByTier[duration] = tierRefundableAmount
    362 accumLeftReward = gnsmath.SafeAddInt64(accumLeftReward, leftReward)
    363 accumCollectedReward = gnsmath.SafeAddInt64(accumCollectedReward, tier.TotalCollectedAmount())
    364 accumTotalDistributeAmount = gnsmath.SafeAddInt64(accumTotalDistributeAmount, tier.TotalDistributeAmount())
    365 }
    366
    367 if accumLeftReward == 0 {
    368 return 0, errors.New("project has no remaining amount")
    369 }
    370
    371 actualTotalDistributeAmount := gnsmath.SafeAddInt64(accumCollectedReward, accumLeftReward)
    372 if accumTotalDistributeAmount != actualTotalDistributeAmount {
    373 return 0, errors.New(ufmt.Sprintf("accumTotalDistributeAmount(%d) != accumCollectedReward(%d)+accumLeftReward(%d)", accumTotalDistributeAmount, accumCollectedReward, accumLeftReward))
    374 }
    375
    376 // Calculate refundable amount: project remaining minus collectable rewards for remaining depositors
    377 projectLeftReward := accumLeftReward
    378 projectRefundableAmount := gnsmath.SafeSubInt64(projectLeftReward, accumCollectableReward)
    379
    380 if projectRefundableAmount < 0 {
    381 return 0, errors.New(ufmt.Sprintf("projectRefundableAmount(%d) < 0", projectRefundableAmount))
    382 }
    383
    384 if projectRefundableAmount > 0 {
    385 updatedTiers := make(map[int64]*launchpad.ProjectTier)
    386 for duration, tier := range project.Tiers() {
    387 tierRefundableAmount := refundableAmountsByTier[duration]
    388 if tierRefundableAmount > 0 {
    389 tier.SetTotalCollectedAmount(gnsmath.SafeAddInt64(tier.TotalCollectedAmount(), tierRefundableAmount))
    390 }
    391
    392 updatedTiers[duration] = tier
    393 }
    394 project.SetTiers(updatedTiers)
    395
    396 common.SafeGRC20Transfer(0, rlm, project.TokenPath(), recipient, projectRefundableAmount)
    397 }
    398
    399 return projectRefundableAmount, nil
    400}
    401
    402// applyTierRewardGrowthWithRewards updates a tier index and returns a
    403// conservative aggregate reservation for active claims. Rounding dust remains
    404// in the project instead of risking an under-reserved depositor claim.
    405func (lp *launchpadV1) applyTierRewardGrowthWithRewards(tier *launchpad.ProjectTier, currentTime int64) (int64, error) {
    406 rewardManager, err := lp.getProjectTierRewardManager(tier.ID())
    407 if err != nil {
    408 return 0, err
    409 }
    410
    411 if err := updateRewardPerDepositX128(rewardManager, getTierCurrentDepositAmount(tier), currentTime); err != nil {
    412 return 0, err
    413 }
    414
    415 emitUpdateLaunchpadRewardAccumulation(tier.ID(), rewardManager, getTierCurrentDepositAmount(tier))
    416
    417 return calculateMaximumClaimableRewardsForActiveDeposits(rewardManager), nil
    418}
    419
    420// validateTransferLeft validates the transfer of remaining tokens
    421func validateRefundProject(project *launchpad.Project, recipient address, currentTime int64) error {
    422 if !recipient.IsValid() {
    423 return errors.New(ufmt.Sprintf("invalid recipient address(%s)", recipient.String()))
    424 }
    425
    426 return validateRefundRemainingAmount(project, currentTime)
    427}
    428
    429type createProjectParams struct {
    430 name string
    431 tokenPath string
    432 recipient address
    433 depositAmount int64
    434 conditionTokens string
    435 conditionAmounts string
    436 tier30Ratio int64
    437 tier90Ratio int64
    438 tier180Ratio int64
    439 startTime int64
    440 currentTime int64
    441 currentHeight int64
    442 minimumStartDelayTime int64
    443}
    444
    445func (p *createProjectParams) validate() error {
    446 if err := p.validateName(); err != nil {
    447 return err
    448 }
    449
    450 if err := p.validateTokenPath(); err != nil {
    451 return err
    452 }
    453
    454 if err := p.validateRecipient(); err != nil {
    455 return err
    456 }
    457
    458 if err := p.validateDepositAmount(); err != nil {
    459 return err
    460 }
    461
    462 if err := p.validateRatio(); err != nil {
    463 return err
    464 }
    465
    466 if err := p.validateStartTime(p.currentTime, p.minimumStartDelayTime); err != nil {
    467 return err
    468 }
    469
    470 if err := p.validateConditions(); err != nil {
    471 return err
    472 }
    473
    474 return nil
    475}
    476
    477// validateName checks if the project name is valid.
    478func (p *createProjectParams) validateName() error {
    479 if p.name == "" {
    480 return makeErrorWithDetails(errInvalidInput, "project name cannot be empty")
    481 }
    482
    483 if len(p.name) > 100 {
    484 return makeErrorWithDetails(errInvalidInput, "project name is too long")
    485 }
    486
    487 return nil
    488}
    489
    490// validateTokenPath validates the token path is not empty and is registered.
    491func (p *createProjectParams) validateTokenPath() error {
    492 if p.tokenPath == "" {
    493 return makeErrorWithDetails(errInvalidInput, "tokenPath cannot be empty")
    494 }
    495
    496 if err := common.IsRegistered(p.tokenPath); err != nil && !isGovernanceToken(p.tokenPath) {
    497 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("tokenPath(%s) not registered", p.tokenPath))
    498 }
    499
    500 return nil
    501}
    502
    503// validateRecipient checks if the recipient address is valid.
    504func (p *createProjectParams) validateRecipient() error {
    505 if !p.recipient.IsValid() {
    506 return makeErrorWithDetails(errInvalidAddress, ufmt.Sprintf("recipient address(%s)", p.recipient.String()))
    507 }
    508
    509 return nil
    510}
    511
    512// validateDepositAmount ensures that the deposit amount is greater than zero.
    513func (p *createProjectParams) validateDepositAmount() error {
    514 if p.depositAmount == 0 {
    515 return makeErrorWithDetails(errInvalidInput, "deposit amount cannot be 0")
    516 }
    517
    518 if p.depositAmount < 0 {
    519 return makeErrorWithDetails(errInvalidInput, "deposit amount cannot be negative")
    520 }
    521
    522 return nil
    523}
    524
    525// validateRatio checks if each tier ratio is non-negative and the sum equals 100.
    526func (p *createProjectParams) validateRatio() error {
    527 if p.tier30Ratio < 0 || p.tier90Ratio < 0 || p.tier180Ratio < 0 {
    528 return makeErrorWithDetails(
    529 errInvalidInput,
    530 ufmt.Sprintf("tier ratios must be non-negative (30:%d, 90:%d, 180:%d)", p.tier30Ratio, p.tier90Ratio, p.tier180Ratio),
    531 )
    532 }
    533
    534 sum := p.tier30Ratio + p.tier90Ratio + p.tier180Ratio
    535 if sum != 100 {
    536 return makeErrorWithDetails(
    537 errInvalidInput,
    538 ufmt.Sprintf("invalid ratio, sum of all tiers(30:%d, 90:%d, 180:%d) should be 100", p.tier30Ratio, p.tier90Ratio, p.tier180Ratio),
    539 )
    540 }
    541
    542 return nil
    543}
    544
    545// validateStartTime checks if the start time is available with minimum delay requirement.
    546func (p *createProjectParams) validateStartTime(now int64, minimumStartDelayTime int64) error {
    547 availableStartTime := now + minimumStartDelayTime
    548
    549 if p.startTime < availableStartTime {
    550 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("start time(%d) must be greater than now(%d)", p.startTime, availableStartTime))
    551 }
    552
    553 return nil
    554}
    555
    556func (p *createProjectParams) validateConditions() error {
    557 if p.conditionTokens == "" && p.conditionAmounts == "" {
    558 return nil
    559 }
    560
    561 tokenPaths := strings.Split(p.conditionTokens, stringSplitterPad)
    562 minimumAmounts := strings.Split(p.conditionAmounts, stringSplitterPad)
    563
    564 if len(tokenPaths) != len(minimumAmounts) {
    565 return makeErrorWithDetails(errInvalidInput, "conditionTokens and conditionAmounts are not matched")
    566 }
    567 if len(tokenPaths) > maxProjectConditionCount {
    568 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("condition count(%d) exceeds maximum(%d)", len(tokenPaths), maxProjectConditionCount))
    569 }
    570
    571 tokenPathMap := make(map[string]bool)
    572
    573 for _, tokenPath := range tokenPaths {
    574 err := common.IsRegistered(tokenPath)
    575 if err != nil && !isGovernanceToken(tokenPath) {
    576 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("tokenPath(%s) not registered", tokenPath))
    577 }
    578
    579 if tokenPathMap[tokenPath] {
    580 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("tokenPath(%s) is duplicated", tokenPath))
    581 }
    582
    583 tokenPathMap[tokenPath] = true
    584 }
    585
    586 for _, amountStr := range minimumAmounts {
    587 minimumAmount, err := strconv.ParseInt(amountStr, 10, 64)
    588 if err != nil {
    589 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("invalid condition amount(%s)", amountStr))
    590 }
    591
    592 if minimumAmount <= 0 {
    593 return makeErrorWithDetails(errInvalidInput, ufmt.Sprintf("condition amount(%s) is not available", amountStr))
    594 }
    595 }
    596
    597 return nil
    598}
    599
    600func isGovernanceToken(tokenPath string) bool {
    601 return tokenPath == GOV_XGNS_PATH
    602}
    603