| 1 | [ |
| 2 | "autonomous agent", |
| 3 | { |
| 4 | "doc_url": "https://city.obyte.org/city.json", |
| 5 | "getters": "{ |
| 6 | |
| 7 | |
| 8 | |
| 9 | $lib_aa = '3LUPAHCQMJCQDKFVZB7GFKYJMHZ5BC67'; |
| 10 | |
| 11 | $year = 31536000; |
| 12 | |
| 13 | $launch_date = '2025-07-01'; |
| 14 | $fundraise_recipient = 'NJ2JFDOOSJVKXIMKA4HFJ5P65FRUDZOA'; |
| 15 | |
| 16 | |
| 17 | $max_randomness_delay = 600; |
| 18 | $matching_timeout = 600; |
| 19 | |
| 20 | |
| 21 | $get_deposited_supply = () => { |
| 22 | $state = var['state']; |
| 23 | $state.total_land |
| 24 | }; |
| 25 | |
| 26 | |
| 27 | $get_variables = () => { |
| 28 | var['variables'] OTHERWISE { |
| 29 | matching_probability: 0.1, |
| 30 | plot_price: 1000e9, |
| 31 | referral_boost: 0.1, |
| 32 | randomness_aa: 'R2OVZFLVPBY65CNRL4W6GKK34XGOOQJS', |
| 33 | randomness_price: 0.001, |
| 34 | p2p_sale_fee: 0.01, |
| 35 | shortcode_sale_fee: 0.01, |
| 36 | rental_surcharge_factor: 1, |
| 37 | followup_reward_share: 0.1, |
| 38 | attestors: 'JBW7HT5CRBSF7J7RD26AYLQG6GZDPFPS:5KM36CFPBD2QJLVD65PHZG34WEM4RPY2', |
| 39 | } |
| 40 | }; |
| 41 | |
| 42 | |
| 43 | $get_randomness_request = $plot_num => { |
| 44 | $plot = var['plot_'||$plot_num]; |
| 45 | require($plot, "no such request"); |
| 46 | require($plot.status == 'pending', "plot already allocated"); |
| 47 | $want_max_security = timestamp < $plot.ts + $max_randomness_delay; |
| 48 | return { |
| 49 | |
| 50 | ts: $plot.ts, |
| 51 | want_max_security: $want_max_security, |
| 52 | }; |
| 53 | }; |
| 54 | |
| 55 | |
| 56 | $get_buy_fee = $city_name => { |
| 57 | $city = var['city_'||($city_name OTHERWISE 'city')]; |
| 58 | require($city, "no such city"); |
| 59 | $variables = $get_variables(); |
| 60 | |
| 61 | $plot_price = $city.plot_price OTHERWISE $variables.plot_price; |
| 62 | $matching_probability = $city.matching_probability OTHERWISE $variables.matching_probability; |
| 63 | $referral_boost = $city.referral_boost OTHERWISE $variables.referral_boost; |
| 64 | $buy_fee = 2 * (1 + $referral_boost) * $matching_probability / (1 - 4 * $matching_probability); |
| 65 | return $buy_fee; |
| 66 | }; |
| 67 | |
| 68 | |
| 69 | $are_neighbors = ($plot1_num, $plot2_num, $allow_same_owner) => { |
| 70 | require($plot1_num < $plot2_num, "not ordered"); |
| 71 | |
| 72 | $plot1 = var['plot_'||$plot1_num]; |
| 73 | require($plot1, "no such plot1"); |
| 74 | require($plot1.status == 'land', "plot1 is not a vacant plot of land, can't build"); |
| 75 | |
| 76 | $plot2 = var['plot_'||$plot2_num]; |
| 77 | require($plot2, "no such plot2"); |
| 78 | require($plot2.status == 'land', "plot2 is not a vacant plot of land, can't build"); |
| 79 | |
| 80 | if (!$allow_same_owner){ |
| 81 | require($plot1.owner != $plot2.owner, "both plots have the same owner"); |
| 82 | require($plot1.username != $plot2.username, "both plots are attested to the same username"); |
| 83 | } |
| 84 | |
| 85 | require($plot1.city == $plot2.city, "plots must be in the same city"); |
| 86 | $city_name = $plot1.city; |
| 87 | $city = var['city_'||$city_name]; |
| 88 | |
| 89 | |
| 90 | if ($plot2.last_transfer_ts) |
| 91 | bounce("the later plot was transferred after matching"); |
| 92 | if ($plot1.last_transfer_ts AND $plot1.last_transfer_ts > $plot2.ts) |
| 93 | bounce("the earlier plot was transferred after matching"); |
| 94 | |
| 95 | |
| 96 | if ($plot1.rental_expiry_ts AND $plot1.rental_expiry_ts - $year > $plot2.ts) |
| 97 | bounce("the earlier plot was expanded by rental after matching"); |
| 98 | |
| 99 | $variables = $get_variables(); |
| 100 | $matching_probability = $city.matching_probability OTHERWISE $variables.matching_probability; |
| 101 | $referral_boost = $city.referral_boost OTHERWISE $variables.referral_boost; |
| 102 | |
| 103 | $is_referrer_match = ($plot2.ref_plot_num AND $plot2.ref_plot_num == $plot1_num) ? 1 : 0; |
| 104 | $plot1_rented_amount = ($plot1.rented_amount AND timestamp < $plot1.rental_expiry_ts) ? $plot1.rented_amount : 0; |
| 105 | |
| 106 | $max_distance = sqrt(1e12 * $matching_probability * (($plot1.amount + $plot1_rented_amount) / ($city.total_land + $city.total_rented) + $is_referrer_match * $referral_boost)) / 2; |
| 107 | $bNeighbors = abs($plot1.x - $plot2.x) <= $max_distance AND abs($plot1.y - $plot2.y) <= $max_distance; |
| 108 | return $bNeighbors; |
| 109 | }; |
| 110 | |
| 111 | }", |
| 112 | "init": "{ |
| 113 | |
| 114 | $lib = $lib_aa||''; |
| 115 | |
| 116 | $followup_reward_days = { |
| 117 | '60': true, |
| 118 | '150': true, |
| 119 | '270': true, |
| 120 | '450': true, |
| 121 | '720': true, |
| 122 | '1080': true, |
| 123 | '1620': true, |
| 124 | }; |
| 125 | $followup_claim_term = 10; |
| 126 | |
| 127 | $bPrelaunch = timestamp < parse_date($launch_date); |
| 128 | |
| 129 | $constants = var['constants'] OTHERWISE {}; |
| 130 | |
| 131 | |
| 132 | $asset = $constants.asset; |
| 133 | |
| 134 | $governance_aa = $constants.governance_aa; |
| 135 | |
| 136 | $variables = $get_variables(); |
| 137 | |
| 138 | $state = var['state'] OTHERWISE { |
| 139 | last_plot_num: 0, |
| 140 | last_house_num: 0, |
| 141 | total_land: 0, |
| 142 | }; |
| 143 | |
| 144 | if ($asset) |
| 145 | $received_amount = trigger.output[[asset=($bPrelaunch AND trigger.data.buy AND !trigger.data.mayor_plot ? 'base' : $asset)]]; |
| 146 | |
| 147 | if (trigger.data.to AND !is_valid_address(trigger.data.to)) |
| 148 | bounce("bad to address"); |
| 149 | $to = trigger.data.to OTHERWISE trigger.address; |
| 150 | $username = attestation[[attestors=$variables.attestors, address=$to, ifnone=false]].username; |
| 151 | |
| 152 | if (trigger.data.plot_num){ |
| 153 | $plot_num = trigger.data.plot_num; |
| 154 | $plot = var['plot_'||$plot_num]; |
| 155 | require($plot, "no such plot"); |
| 156 | require($plot.status == 'land', "not a vacant plot of land"); |
| 157 | } |
| 158 | |
| 159 | |
| 160 | $governance_base_aa = 'JGGFM55N6626QBQWAYBHMBN6A76TVPK5'; |
| 161 | |
| 162 | }", |
| 163 | "messages": { |
| 164 | "cases": [ |
| 165 | { |
| 166 | "if": "{ trigger.data.define AND !$asset }", |
| 167 | "messages": [ |
| 168 | { |
| 169 | "app": "asset", |
| 170 | "payload": { |
| 171 | "is_private": false, |
| 172 | "is_transferrable": true, |
| 173 | "auto_destroy": false, |
| 174 | "fixed_denominations": false, |
| 175 | "issued_by_definer_only": true, |
| 176 | "cosigned_by_definer": false, |
| 177 | "spender_attested": false |
| 178 | } |
| 179 | }, |
| 180 | { |
| 181 | "app": "definition", |
| 182 | "payload": { |
| 183 | "definition": [ |
| 184 | "autonomous agent", |
| 185 | { |
| 186 | "base_aa": "{$governance_base_aa}", |
| 187 | "params": { |
| 188 | "city_aa": "{this_address}" |
| 189 | } |
| 190 | } |
| 191 | ] |
| 192 | } |
| 193 | }, |
| 194 | { |
| 195 | "app": "state", |
| 196 | "state": "{ |
| 197 | $constants.asset = response_unit; |
| 198 | $constants.governance_aa = unit[response_unit].messages[[.app='definition']].payload.address; |
| 199 | var['constants'] = $constants; |
| 200 | var['city_city'] = { |
| 201 | count_plots: 0, |
| 202 | count_houses: 0, |
| 203 | total_land: 0, |
| 204 | total_bought: 0, |
| 205 | total_rented: 0, |
| 206 | start_ts: 0, |
| 207 | mayor: $fundraise_recipient, |
| 208 | }; |
| 209 | response['asset'] = response_unit; |
| 210 | }" |
| 211 | } |
| 212 | ] |
| 213 | }, |
| 214 | { |
| 215 | "if": "{ trigger.address == $governance_aa AND trigger.data.name }", |
| 216 | "init": "{ |
| 217 | $name = trigger.data.name; |
| 218 | $value = trigger.data.value; |
| 219 | $city_name = trigger.data.city; |
| 220 | }", |
| 221 | "messages": [ |
| 222 | { |
| 223 | "app": "state", |
| 224 | "state": "{ |
| 225 | if ($city_name){ |
| 226 | if ($name == 'new_city') |
| 227 | $city = { |
| 228 | count_plots: 0, |
| 229 | count_houses: 0, |
| 230 | total_land: 0, |
| 231 | total_bought: 0, |
| 232 | total_rented: 0, |
| 233 | start_ts: 0, |
| 234 | mayor: trigger.data.mayor, |
| 235 | }; |
| 236 | else{ |
| 237 | $city = var['city_'||$city_name]; |
| 238 | $city[$name] = $value; |
| 239 | } |
| 240 | var['city_'||$city_name] = $city; |
| 241 | } |
| 242 | else{ |
| 243 | $variables[$name] = $value; |
| 244 | var['variables'] = $variables; |
| 245 | } |
| 246 | }" |
| 247 | } |
| 248 | ] |
| 249 | }, |
| 250 | { |
| 251 | "if": "{ |
| 252 | $buy_from_balance = trigger.data.buy_from_balance; |
| 253 | ($received_amount > 0 OR $buy_from_balance OR trigger.data.mayor_plot) AND trigger.data.buy |
| 254 | }", |
| 255 | "init": "{ |
| 256 | $city_name = trigger.data.city OTHERWISE 'city'; |
| 257 | $city = var['city_'||$city_name]; |
| 258 | require($city, "no such city"); |
| 259 | require($username, "your address must be attested"); |
| 260 | |
| 261 | $plot_price = $city.plot_price OTHERWISE $variables.plot_price; |
| 262 | $matching_probability = $city.matching_probability OTHERWISE $variables.matching_probability; |
| 263 | $referral_boost = $city.referral_boost OTHERWISE $variables.referral_boost; |
| 264 | $buy_fee = 2 * (1 + $referral_boost) * $matching_probability / (1 - 4 * $matching_probability); |
| 265 | |
| 266 | $price = ceil($plot_price * (1 + $buy_fee)); |
| 267 | if ($buy_from_balance){ |
| 268 | require($to == trigger.address, "owner cannot be redefined when buying from balance"); |
| 269 | require($received_amount == 0, "do not send coins when buying from balance"); |
| 270 | $balance = var['balance_'||trigger.address] OTHERWISE 0; |
| 271 | require($balance >= $price, "not enough balance to buy"); |
| 272 | } |
| 273 | else if (trigger.data.mayor_plot AND trigger.address == $city.mayor){ |
| 274 | require($received_amount == 0, "do not send coins when creating a mayor plot"); |
| 275 | $bMayorPlot = true; |
| 276 | } |
| 277 | else{ |
| 278 | $bought_tokens = $bPrelaunch ? floor($plot_price * 0.1) : 0; |
| 279 | $expected_amount = round(($price + $bought_tokens)/($bPrelaunch ? 1000 : 1)); |
| 280 | require($received_amount == $expected_amount, "incorrect amount received, expected "||$expected_amount); |
| 281 | } |
| 282 | }", |
| 283 | "messages": [ |
| 284 | { |
| 285 | "if": "{$bought_tokens}", |
| 286 | "app": "payment", |
| 287 | "payload": { |
| 288 | "asset": "{$asset}", |
| 289 | "outputs": [ |
| 290 | { |
| 291 | "address": "{$to}", |
| 292 | "amount": "{$bought_tokens}" |
| 293 | } |
| 294 | ] |
| 295 | } |
| 296 | }, |
| 297 | { |
| 298 | "app": "state", |
| 299 | "state": "{ |
| 300 | if ($buy_from_balance) |
| 301 | var['balance_'||trigger.address] -= $price; |
| 302 | $new_plot_num = $state.last_plot_num + 1; |
| 303 | $state.last_plot_num = $new_plot_num; |
| 304 | $new_plot = { |
| 305 | status: 'pending', |
| 306 | amount: 0, |
| 307 | city: $city_name, |
| 308 | ts: timestamp, |
| 309 | }; |
| 310 | if (!$bMayorPlot){ |
| 311 | $new_plot.owner = $to; |
| 312 | $new_plot.username = $username; |
| 313 | $new_plot.amount = $plot_price; |
| 314 | if ($city.total_bought == 0) |
| 315 | $city.start_ts = timestamp; |
| 316 | $state.total_land = $state.total_land + $plot_price; |
| 317 | $city.count_plots = $city.count_plots + 1; |
| 318 | $city.total_land = $city.total_land + $plot_price; |
| 319 | $city.total_bought = $city.total_bought + $plot_price; |
| 320 | var['user_land_'||$to] += $plot_price; |
| 321 | var['user_land_'||$city_name||'_'||$to] += $plot_price; |
| 322 | $ref = trigger.data.ref; |
| 323 | if (($ref OR trigger.data.ref_plot_num) AND $received_amount){ |
| 324 | if (trigger.data.ref_plot_num) |
| 325 | $new_plot.ref_plot_num = trigger.data.ref_plot_num; |
| 326 | else{ |
| 327 | require(is_valid_address($ref), "invalid referrer address"); |
| 328 | $new_plot.ref = $ref; |
| 329 | $ref_plot_num = var['user_main_plot_'||$city_name||'_'||$ref]; |
| 330 | if ($ref_plot_num) |
| 331 | $new_plot.ref_plot_num = $ref_plot_num; |
| 332 | else |
| 333 | response['warning'] = "The referring user has no main plot"; |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | var['plot_'||$new_plot_num] = $new_plot; |
| 338 | var['city_'||$city_name] = $city; |
| 339 | var['state'] = $state; |
| 340 | response['message'] = "Order received, now wait for randomness to determine your new plot's coordinates"; |
| 341 | response['plot_num'] = $new_plot_num; |
| 342 | response['event'] = json_stringify({type: 'buy', plot_num: $new_plot_num, owner: $new_plot.owner, amount: $new_plot.amount, city: $city_name, from_balance: $buy_from_balance, mayor_plot: $bMayorPlot}); |
| 343 | }" |
| 344 | } |
| 345 | ] |
| 346 | }, |
| 347 | { |
| 348 | "if": "{ trigger.data.rand AND trigger.data.req_id AND trigger.address == $variables.randomness_aa }", |
| 349 | "init": "{ |
| 350 | $new_plot_num = trigger.data.req_id; |
| 351 | $new_plot = var['plot_'||$new_plot_num]; |
| 352 | require($new_plot.status == 'pending', "plot already received"); |
| 353 | $new_plot.status = 'land'; |
| 354 | |
| 355 | $n = number_from_seed(trigger.data.rand, 1e12 - 1); |
| 356 | $x = floor($n / 1e6); |
| 357 | $y = $n % 1e6; |
| 358 | $new_plot.x = $x; |
| 359 | $new_plot.y = $y; |
| 360 | }", |
| 361 | "messages": [ |
| 362 | { |
| 363 | "app": "state", |
| 364 | "state": "{ |
| 365 | var['plot_'||$new_plot_num] = $new_plot.amount ? $new_plot : false; |
| 366 | $city_name = $new_plot.city; |
| 367 | if ($new_plot.amount){ |
| 368 | response['event'] = json_stringify({type: 'allocate', plot_num: $new_plot_num, x: $x, y: $y, city: $city_name}); |
| 369 | } |
| 370 | else{ |
| 371 | $state.last_house_num = $state.last_house_num + 2; |
| 372 | var['house_'||$state.last_house_num] = { |
| 373 | plot_num: $new_plot_num, |
| 374 | |
| 375 | amount: 0, |
| 376 | city: $city_name, |
| 377 | x: $x, |
| 378 | y: $y, |
| 379 | ts: timestamp, |
| 380 | plot_ts: $new_plot.ts, |
| 381 | info: $new_plot.info, |
| 382 | }; |
| 383 | var['state'] = $state; |
| 384 | response['event'] = json_stringify({type: 'house', house_num: $state.last_house_num, plot_num: $new_plot_num, amount: 0, x: $x, y: $y, city: $city_name}); |
| 385 | } |
| 386 | $city = var['city_'||$city_name]; |
| 387 | var['rand_provider_balance_'||$variables.randomness_aa] += floor(($new_plot.amount OTHERWISE $city.plot_price OTHERWISE $variables.plot_price) * $variables.randomness_price); |
| 388 | }" |
| 389 | } |
| 390 | ] |
| 391 | }, |
| 392 | { |
| 393 | "if": "{ |
| 394 | $randomness_aa = trigger.data.randomness_aa; |
| 395 | trigger.data.withdraw_rand_provider_earnings AND $randomness_aa |
| 396 | }", |
| 397 | "init": "{ |
| 398 | $balance = var['rand_provider_balance_'||$randomness_aa]; |
| 399 | require($balance, "no balance"); |
| 400 | }", |
| 401 | "messages": [ |
| 402 | { |
| 403 | "app": "payment", |
| 404 | "payload": { |
| 405 | "asset": "{$asset}", |
| 406 | "outputs": [ |
| 407 | { |
| 408 | "address": "{$randomness_aa}", |
| 409 | "amount": "{$balance}" |
| 410 | } |
| 411 | ] |
| 412 | } |
| 413 | }, |
| 414 | { |
| 415 | "app": "state", |
| 416 | "state": "{ |
| 417 | var['rand_provider_balance_'||$randomness_aa] = 0; |
| 418 | }" |
| 419 | } |
| 420 | ] |
| 421 | }, |
| 422 | { |
| 423 | "if": "{trigger.data.leave AND $plot_num}", |
| 424 | "init": "{ |
| 425 | require($plot.owner == trigger.address, "you are not the owner"); |
| 426 | $amount = $plot.amount; |
| 427 | $city_name = $plot.city; |
| 428 | $city = var['city_'||$city_name]; |
| 429 | }", |
| 430 | "messages": [ |
| 431 | { |
| 432 | "app": "payment", |
| 433 | "payload": { |
| 434 | "asset": "{$asset}", |
| 435 | "outputs": [ |
| 436 | { |
| 437 | "address": "{trigger.address}", |
| 438 | "amount": "{$amount}" |
| 439 | } |
| 440 | ] |
| 441 | } |
| 442 | }, |
| 443 | { |
| 444 | "app": "payment", |
| 445 | "payload": { |
| 446 | "asset": "base", |
| 447 | "outputs": [ |
| 448 | { |
| 449 | "address": "{$governance_aa}", |
| 450 | "amount": 1000 |
| 451 | } |
| 452 | ] |
| 453 | } |
| 454 | }, |
| 455 | { |
| 456 | "app": "data", |
| 457 | "payload": { |
| 458 | "update_user_balance": 1, |
| 459 | "address": "{trigger.address}", |
| 460 | "city": "{$city_name}" |
| 461 | } |
| 462 | }, |
| 463 | { |
| 464 | "app": "state", |
| 465 | "state": "{ |
| 466 | var['plot_'||$plot_num] = false; |
| 467 | $city.count_plots = $city.count_plots - 1; |
| 468 | $city.total_land = $city.total_land - $amount; |
| 469 | $state.total_land = $state.total_land - $amount; |
| 470 | var['city_'||$city_name] = $city; |
| 471 | var['user_land_'||trigger.address] -= $amount; |
| 472 | var['user_land_'||$city_name||'_'||trigger.address] -= $amount; |
| 473 | var['state'] = $state; |
| 474 | response['message'] = "Left plot"; |
| 475 | response['event'] = json_stringify({type: 'leave', plot_num: $plot_num, city: $city_name}); |
| 476 | }" |
| 477 | } |
| 478 | ] |
| 479 | }, |
| 480 | { |
| 481 | "if": "{ |
| 482 | $plot1_num = trigger.data.plot1_num; |
| 483 | $plot2_num = trigger.data.plot2_num; |
| 484 | trigger.data.build AND $plot1_num AND $plot2_num |
| 485 | }", |
| 486 | "init": "{ |
| 487 | require($plot1_num < $plot2_num, "not ordered"); |
| 488 | |
| 489 | $plot1 = var['plot_'||$plot1_num]; |
| 490 | require($plot1, "no such plot1"); |
| 491 | require($plot1.status == 'land', "plot1 is not a vacant plot of land, can't build"); |
| 492 | $owner1 = $plot1.owner; |
| 493 | $bOwnerOf1 = $owner1 == trigger.address; |
| 494 | |
| 495 | $plot2 = var['plot_'||$plot2_num]; |
| 496 | require($plot2, "no such plot2"); |
| 497 | require($plot2.status == 'land', "plot2 is not a vacant plot of land, can't build"); |
| 498 | $owner2 = $plot2.owner; |
| 499 | $bOwnerOf2 = $owner2 == trigger.address; |
| 500 | |
| 501 | require($bOwnerOf1 OR $bOwnerOf2, "you are not the owner of any plot"); |
| 502 | if ($bOwnerOf1 AND $bOwnerOf2) |
| 503 | bounce("you are the owner of both plots"); |
| 504 | require($plot1.username != $plot2.username, "both plots are attested to the same username"); |
| 505 | |
| 506 | require($plot1.city == $plot2.city, "plots must be in the same city"); |
| 507 | $city_name = $plot1.city; |
| 508 | $city = var['city_'||$city_name]; |
| 509 | |
| 510 | |
| 511 | if ($plot2.last_transfer_ts) |
| 512 | bounce("the later plot was transferred after matching"); |
| 513 | if ($plot1.last_transfer_ts AND $plot1.last_transfer_ts > $plot2.ts) |
| 514 | bounce("the earlier plot was transferred after matching"); |
| 515 | |
| 516 | |
| 517 | if ($plot1.rental_expiry_ts AND $plot1.rental_expiry_ts - $year > $plot2.ts) |
| 518 | bounce("the earlier plot was expanded by rental after matching"); |
| 519 | |
| 520 | $matching_probability = $city.matching_probability OTHERWISE $variables.matching_probability; |
| 521 | $referral_boost = $city.referral_boost OTHERWISE $variables.referral_boost; |
| 522 | |
| 523 | $is_referrer_match = ($plot2.ref_plot_num AND $plot2.ref_plot_num == $plot1_num) ? 1 : 0; |
| 524 | $plot1_rented_amount = ($plot1.rented_amount AND timestamp < $plot1.rental_expiry_ts) ? $plot1.rented_amount : 0; |
| 525 | |
| 526 | $max_distance = sqrt(1e12 * $matching_probability * (($plot1.amount + $plot1_rented_amount) / ($city.total_land + $city.total_rented) + $is_referrer_match * $referral_boost)) / 2; |
| 527 | $bNeighbors = abs($plot1.x - $plot2.x) <= $max_distance AND abs($plot1.y - $plot2.y) <= $max_distance; |
| 528 | require($bNeighbors, "not neighbors"); |
| 529 | }", |
| 530 | "messages": [ |
| 531 | { |
| 532 | "app": "state", |
| 533 | "state": "{ |
| 534 | $key = 'match_'||$plot1_num||'_'||$plot2_num; |
| 535 | $match = var[$key]; |
| 536 | if (!$match){ |
| 537 | $new_match = {first: trigger.address, ts: timestamp}; |
| 538 | response['message'] = "Registered your request. Your neighbor must send their request within 10 minutes, otherwise you both will have to start over."; |
| 539 | } |
| 540 | else{ |
| 541 | require(!$match.built_ts, "houses already built"); |
| 542 | if ($match.first == trigger.address){ |
| 543 | $match.ts = timestamp; |
| 544 | response['message'] = "Refreshed your request. Your neighbor must send their request within 10 minutes, otherwise you both will have to start over."; |
| 545 | } |
| 546 | else{ |
| 547 | $bInTime = timestamp < $match.ts + $matching_timeout; |
| 548 | if (!$bInTime){ |
| 549 | $match.ts = timestamp; |
| 550 | $match.first = trigger.address; |
| 551 | response['message'] = "Unfortunately, you are too late. Your neighbor has to send their request again within 10 minutes, otherwise you both will have to start over."; |
| 552 | } |
| 553 | else{ |
| 554 | $match.built_ts = timestamp; |
| 555 | $amount = min($plot1.amount, $plot2.amount); |
| 556 | $plot1.amount = $plot1.amount - $amount; |
| 557 | $plot2.amount = $plot2.amount - $amount; |
| 558 | $house1_num = $state.last_house_num + 1; |
| 559 | $house2_num = $house1_num + 1; |
| 560 | $state.last_house_num = $house2_num; |
| 561 | var['house_'||$house1_num] = { |
| 562 | plot_num: $plot1_num, |
| 563 | owner: $owner1, |
| 564 | amount: $amount, |
| 565 | city: $city_name, |
| 566 | x: $plot1.x, |
| 567 | y: $plot1.y, |
| 568 | ts: timestamp, |
| 569 | plot_ts: $plot1.ts, |
| 570 | info: $plot1.info, |
| 571 | }; |
| 572 | var['house_'||$house2_num] = { |
| 573 | plot_num: $plot2_num, |
| 574 | owner: $owner2, |
| 575 | amount: $amount, |
| 576 | city: $city_name, |
| 577 | x: $plot2.x, |
| 578 | y: $plot2.y, |
| 579 | ts: timestamp, |
| 580 | plot_ts: $plot2.ts, |
| 581 | info: $plot2.info, |
| 582 | }; |
| 583 | $events = [ |
| 584 | {type: 'house', house_num: $house1_num, plot_num: $plot1_num, owner: $owner1, amount: $amount, city: $city_name, x: $plot1.x, y: $plot1.y}, |
| 585 | {type: 'house', house_num: $house2_num, plot_num: $plot2_num, owner: $owner2, amount: $amount, city: $city_name, x: $plot2.x, y: $plot2.y}, |
| 586 | ]; |
| 587 | |
| 588 | var['plot_'||$plot1_num] = ($plot1.amount == 0) ? false : $plot1; |
| 589 | var['plot_'||$plot2_num] = ($plot2.amount == 0) ? false : $plot2; |
| 590 | |
| 591 | $lost_rented_amount = (($plot1.amount == 0 AND $plot1.rented_amount) ? $plot1.rented_amount : 0) + (($plot2.amount == 0 AND $plot2.rented_amount) ? $plot2.rented_amount : 0); |
| 592 | if ($lost_rented_amount) |
| 593 | $city.total_rented = $city.total_rented - $lost_rented_amount; |
| 594 | |
| 595 | |
| 596 | $p1 = { |
| 597 | status: 'pending', |
| 598 | owner: $owner1, |
| 599 | username: $plot1.username, |
| 600 | amount: $amount, |
| 601 | city: $city_name, |
| 602 | ts: timestamp, |
| 603 | }; |
| 604 | $p2 = { |
| 605 | status: 'pending', |
| 606 | owner: $owner2, |
| 607 | username: $plot2.username, |
| 608 | amount: $amount, |
| 609 | city: $city_name, |
| 610 | ts: timestamp, |
| 611 | }; |
| 612 | $last_plot_num = $state.last_plot_num; |
| 613 | foreach([1,2,3,4], 4, $offset => { |
| 614 | $new_plot_num = $last_plot_num + $offset; |
| 615 | $p = $offset <= 2 ? $p1 : $p2; |
| 616 | var['plot_'||$new_plot_num] = $p; |
| 617 | $events[] = {type: 'reward', plot_num: $new_plot_num, owner: $p.owner, amount: $amount, city: $city_name}; |
| 618 | }); |
| 619 | |
| 620 | |
| 621 | $main_plot_num1 = var['user_main_plot_'||$city_name||'_'||$owner1]; |
| 622 | $main_plot_num2 = var['user_main_plot_'||$city_name||'_'||$owner2]; |
| 623 | if ($main_plot_num1 AND $main_plot_num1 == $plot1_num) |
| 624 | var['user_main_plot_'||$city_name||'_'||$owner1] = $last_plot_num + 1; |
| 625 | if ($main_plot_num2 AND $main_plot_num2 == $plot2_num) |
| 626 | var['user_main_plot_'||$city_name||'_'||$owner2] = $last_plot_num + 3; |
| 627 | |
| 628 | $state.last_plot_num = $last_plot_num + 4; |
| 629 | $state.total_land = $state.total_land + 2 * $amount; |
| 630 | $city.count_plots = $city.count_plots + 2; |
| 631 | $city.count_houses = $city.count_houses + 2; |
| 632 | $city.total_land = $city.total_land + 2 * $amount; |
| 633 | |
| 634 | var['city_'||$city_name] = $city; |
| 635 | var['state'] = $state; |
| 636 | var['user_houses_'||$owner1] += 1; |
| 637 | var['user_houses_'||$owner2] += 1; |
| 638 | var['user_houses_'||$city_name||'_'||$owner1] += 1; |
| 639 | var['user_houses_'||$city_name||'_'||$owner2] += 1; |
| 640 | var['user_land_'||$owner1] += $amount; |
| 641 | var['user_land_'||$owner2] += $amount; |
| 642 | var['user_land_'||$city_name||'_'||$owner1] += $amount; |
| 643 | var['user_land_'||$city_name||'_'||$owner2] += $amount; |
| 644 | |
| 645 | response['message'] = "Now you've built a house on your land and will receive two new plots of land. Please wait a few minutes for the plots to be randomly allocated."; |
| 646 | response['events'] = json_stringify($events); |
| 647 | } |
| 648 | } |
| 649 | } |
| 650 | var[$key] = $match OTHERWISE $new_match; |
| 651 | }" |
| 652 | } |
| 653 | ] |
| 654 | }, |
| 655 | { |
| 656 | "if": "{trigger.data.sell AND $plot_num}", |
| 657 | "init": "{ |
| 658 | $sale_price = trigger.data.sale_price; |
| 659 | require($plot.owner == trigger.address, "you are not the owner"); |
| 660 | require($sale_price > $plot.amount OR $sale_price == 0, "bad sale price"); |
| 661 | }", |
| 662 | "messages": [ |
| 663 | { |
| 664 | "app": "state", |
| 665 | "state": "{ |
| 666 | response['message'] = $sale_price ? 'Put on sale' : 'Withdrawn from sale'; |
| 667 | response['event'] = json_stringify({type: 'p2p-sell', plot_num: $plot_num, sale_price: $sale_price}); |
| 668 | $plot.sale_price = $sale_price; |
| 669 | var['plot_'||$plot_num] = $plot; |
| 670 | }" |
| 671 | } |
| 672 | ] |
| 673 | }, |
| 674 | { |
| 675 | "if": "{ |
| 676 | $p2p_buy = trigger.data.p2p_buy AND !trigger.data.to AND $received_amount > 0; |
| 677 | $plot_num AND (trigger.data.transfer OR $p2p_buy) |
| 678 | }", |
| 679 | "init": "{ |
| 680 | $old_owner = $plot.owner; |
| 681 | if ($p2p_buy){ |
| 682 | require($old_owner != trigger.address, "you are already the owner"); |
| 683 | require($plot.sale_price, "not on sale"); |
| 684 | require($plot.sale_price == $received_amount, "wrong amount"); |
| 685 | $fee = ceil($variables.p2p_sale_fee * $plot.sale_price); |
| 686 | } |
| 687 | else{ |
| 688 | require($old_owner == trigger.address, "you are not the owner"); |
| 689 | require($to != $old_owner, "same owner"); |
| 690 | } |
| 691 | require($username, "new owner's address must be attested"); |
| 692 | }", |
| 693 | "messages": [ |
| 694 | { |
| 695 | "if": "{$p2p_buy}", |
| 696 | "app": "payment", |
| 697 | "payload": { |
| 698 | "asset": "{$asset}", |
| 699 | "outputs": [ |
| 700 | { |
| 701 | "address": "{$old_owner}", |
| 702 | "amount": "{$received_amount - $fee}" |
| 703 | } |
| 704 | ] |
| 705 | } |
| 706 | }, |
| 707 | { |
| 708 | "app": "payment", |
| 709 | "payload": { |
| 710 | "asset": "base", |
| 711 | "outputs": [ |
| 712 | { |
| 713 | "address": "{$governance_aa}", |
| 714 | "amount": 1000 |
| 715 | } |
| 716 | ] |
| 717 | } |
| 718 | }, |
| 719 | { |
| 720 | "app": "data", |
| 721 | "payload": { |
| 722 | "update_user_balance": 1, |
| 723 | "address": "{$old_owner}", |
| 724 | "city": "{$plot.city}" |
| 725 | } |
| 726 | }, |
| 727 | { |
| 728 | "app": "state", |
| 729 | "state": "{ |
| 730 | if ($p2p_buy){ |
| 731 | $new_owner = trigger.address; |
| 732 | response['message'] = 'Bought'; |
| 733 | response['event'] = json_stringify({type: 'p2p-buy', plot_num: $plot_num, amount: $plot.amount, sale_price: $received_amount, fee: $fee, old_owner: $old_owner, new_owner: $new_owner}); |
| 734 | } |
| 735 | else{ |
| 736 | $new_owner = $to; |
| 737 | response['message'] = 'Transferred'; |
| 738 | response['event'] = json_stringify({type: 'transfer', plot_num: $plot_num, old_owner: $old_owner, new_owner: $to}); |
| 739 | } |
| 740 | $plot.owner = $new_owner; |
| 741 | $plot.username = $username; |
| 742 | $plot.last_transfer_ts = timestamp; |
| 743 | delete($plot, 'sale_price'); |
| 744 | |
| 745 | var['plot_'||$plot_num] = $plot; |
| 746 | var['user_land_'||$new_owner] += $plot.amount; |
| 747 | var['user_land_'||$old_owner] -= $plot.amount; |
| 748 | var['user_land_'||$plot.city||'_'||$new_owner] += $plot.amount; |
| 749 | var['user_land_'||$plot.city||'_'||$old_owner] -= $plot.amount; |
| 750 | }" |
| 751 | } |
| 752 | ] |
| 753 | }, |
| 754 | { |
| 755 | "if": "{ |
| 756 | $rented_amount = trigger.data.rented_amount; |
| 757 | trigger.data.rent AND $plot_num AND $rented_amount AND $received_amount > 0 |
| 758 | }", |
| 759 | "init": "{ |
| 760 | require($plot.owner == trigger.address, "you are not the owner"); |
| 761 | $city = var['city_'||$plot.city]; |
| 762 | $matching_probability = $city.matching_probability OTHERWISE $variables.matching_probability; |
| 763 | $plot_price = $city.plot_price OTHERWISE $variables.plot_price; |
| 764 | |
| 765 | $count_bought = $city.total_bought / $plot_price; |
| 766 | require($count_bought > 10, "too few plots bought yet"); |
| 767 | $old_rented_amount = $plot.rented_amount; |
| 768 | $total_working = $city.total_land + $city.total_rented + $rented_amount - $old_rented_amount; |
| 769 | $elapsed = timestamp - $city.start_ts; |
| 770 | $count_buys_next_year = $year/$elapsed * $count_bought; |
| 771 | |
| 772 | $income_from_one_buy_per_rented_token = 2 * $plot_price * $matching_probability / $total_working; |
| 773 | $fee_per_rented_token = $variables.rental_surcharge_factor * $income_from_one_buy_per_rented_token * $count_buys_next_year; |
| 774 | $rental_fee = ceil($fee_per_rented_token * $rented_amount); |
| 775 | if ($old_rented_amount AND timestamp < $plot.rental_expiry_ts){ |
| 776 | require($rented_amount >= $old_rented_amount, "rental amount cannot be decreased"); |
| 777 | $unused_rent = floor($fee_per_rented_token * $old_rented_amount * ($plot.rental_expiry_ts - timestamp) / $year); |
| 778 | } |
| 779 | $required_fee = $rental_fee - $unused_rent; |
| 780 | $excess = $received_amount - $required_fee; |
| 781 | require($excess >= 0, "not enough paid for rental fee, required "||$required_fee); |
| 782 | }", |
| 783 | "messages": [ |
| 784 | { |
| 785 | "app": "payment", |
| 786 | "payload": { |
| 787 | "asset": "{$asset}", |
| 788 | "outputs": [ |
| 789 | { |
| 790 | "address": "{trigger.address}", |
| 791 | "amount": "{$excess}" |
| 792 | } |
| 793 | ] |
| 794 | } |
| 795 | }, |
| 796 | { |
| 797 | "app": "state", |
| 798 | "state": "{ |
| 799 | response['message'] = 'Rented'; |
| 800 | $city.total_rented = $city.total_rented + $rented_amount - $old_rented_amount; |
| 801 | var['city_'||$plot.city] = $city; |
| 802 | $plot.rented_amount = $rented_amount; |
| 803 | $plot.rental_expiry_ts = timestamp + $year; |
| 804 | response['event'] = json_stringify({type: 'rent', plot_num: $plot_num, rented_amount: $rented_amount, rental_expiry_ts: $plot.rental_expiry_ts, rental_fee: $rental_fee}); |
| 805 | var['plot_'||$plot_num] = $plot; |
| 806 | }" |
| 807 | } |
| 808 | ] |
| 809 | }, |
| 810 | { |
| 811 | "if": "{trigger.data.end_rental AND $plot_num}", |
| 812 | "init": "{ |
| 813 | require($plot.rented_amount AND timestamp > $plot.rental_expiry_ts, "rental is still active"); |
| 814 | $city = var['city_'||$plot.city]; |
| 815 | }", |
| 816 | "messages": [ |
| 817 | { |
| 818 | "app": "state", |
| 819 | "state": "{ |
| 820 | response['message'] = 'Ended rental'; |
| 821 | $city.total_rented = $city.total_rented - $plot.rented_amount; |
| 822 | var['city_'||$plot.city] = $city; |
| 823 | response['event'] = json_stringify({type: 'end_rental', plot_num: $plot_num, rented_amount: $plot.rented_amount}); |
| 824 | $plot.rented_amount = 0; |
| 825 | var['plot_'||$plot_num] = $plot; |
| 826 | }" |
| 827 | } |
| 828 | ] |
| 829 | }, |
| 830 | { |
| 831 | "if": "{ |
| 832 | $days = trigger.data.days; |
| 833 | $house1_num = trigger.data.house1_num; |
| 834 | $house2_num = trigger.data.house2_num; |
| 835 | trigger.data.followup AND $days AND $house1_num AND $house2_num |
| 836 | }", |
| 837 | "init": "{ |
| 838 | require($followup_reward_days[$days], "no such follow-up"); |
| 839 | $house1 = var['house_'||$house1_num]; |
| 840 | $house2 = var['house_'||$house2_num]; |
| 841 | |
| 842 | $key = 'followup_'||$house1_num||'_'||$house2_num; |
| 843 | |
| 844 | $fu = var[$key] OTHERWISE {reward: floor($variables.followup_reward_share * $house1.amount)}; |
| 845 | |
| 846 | $res = $lib#0.$claim_followup_reward($house1, $house2, $house1_num, $house2_num, $days||'', trigger.address, $followup_claim_term, $matching_timeout, $fu); |
| 847 | }", |
| 848 | "messages": [ |
| 849 | { |
| 850 | "app": "state", |
| 851 | "state": "{ |
| 852 | response['message'] = $res.message; |
| 853 | if ($res.bPaid){ |
| 854 | var['balance_'||$house1.owner] += $fu.reward; |
| 855 | var['balance_'||$house2.owner] += $fu.reward; |
| 856 | response['event'] = $res.event; |
| 857 | } |
| 858 | var[$key] = $res.fu; |
| 859 | }" |
| 860 | } |
| 861 | ] |
| 862 | }, |
| 863 | { |
| 864 | "if": "{trigger.data.edit_house AND trigger.data.house_num}", |
| 865 | "init": "{ |
| 866 | $house_num = trigger.data.house_num; |
| 867 | $house = var['house_'||$house_num]; |
| 868 | require($house, "no such house"); |
| 869 | $city = var['city_'||$house.city]; |
| 870 | require($house.owner AND $house.owner == trigger.address OR $house.amount == 0 AND $city.mayor == trigger.address, "you are not the owner"); |
| 871 | }", |
| 872 | "messages": [ |
| 873 | { |
| 874 | "app": "state", |
| 875 | "state": "{ |
| 876 | $shortcode = trigger.data.shortcode; |
| 877 | if (($shortcode OR trigger.data.release_shortcode) AND $house.shortcode){ |
| 878 | var['shortcode_'||$house.shortcode] = false; |
| 879 | $house.shortcode = ''; |
| 880 | } |
| 881 | if ($shortcode){ |
| 882 | require($house.amount, "mayor houses cannot be assigned shortcodes"); |
| 883 | require($house.amount >= $variables.plot_price, "the plot is too cheap"); |
| 884 | require(typeof($shortcode) == 'string', "shortcode must be a string"); |
| 885 | require(has_only($shortcode, "a-z0-9_.-"), "shortcode is allowed to include only lowercase latin letters, numbers, -, _, and ."); |
| 886 | require(!var['shortcode_'||$shortcode], "this shortcode is already taken"); |
| 887 | |
| 888 | |
| 889 | |
| 890 | var['shortcode_'||$shortcode] = $to; |
| 891 | $house.shortcode = $shortcode; |
| 892 | } |
| 893 | if (trigger.data.sell_shortcode AND is_integer(trigger.data.shortcode_price) AND trigger.data.shortcode_price >= 0) |
| 894 | $house.shortcode_price = trigger.data.shortcode_price; |
| 895 | if (exists(trigger.data.new_owner) AND $house.amount == 0) |
| 896 | $house.owner = trigger.data.new_owner; |
| 897 | if (trigger.data.info) |
| 898 | $house.info = trigger.data.info; |
| 899 | response['message'] = 'Edited house'; |
| 900 | response['event'] = json_stringify({type: 'edit_house', house_num: $house_num, info: trigger.data.info, shortcode: $shortcode, release_shortcode: trigger.data.release_shortcode}); |
| 901 | var['house_'||$house_num] = $house; |
| 902 | }" |
| 903 | } |
| 904 | ] |
| 905 | }, |
| 906 | { |
| 907 | "if": "{ |
| 908 | $seller_house_num = trigger.data.seller_house_num; |
| 909 | $my_house_num = trigger.data.my_house_num; |
| 910 | trigger.data.p2p_buy_shortcode AND $seller_house_num AND $my_house_num AND $received_amount > 0 |
| 911 | }", |
| 912 | "init": "{ |
| 913 | $seller_house = var['house_'||$seller_house_num]; |
| 914 | $my_house = var['house_'||$my_house_num]; |
| 915 | |
| 916 | $res = $lib#0.$buy_shortcode($seller_house, $my_house, $seller_house_num, $my_house_num, trigger.address, $received_amount, $variables); |
| 917 | }", |
| 918 | "messages": [ |
| 919 | { |
| 920 | "app": "payment", |
| 921 | "payload": { |
| 922 | "asset": "{$asset}", |
| 923 | "outputs": [ |
| 924 | { |
| 925 | "address": "{$seller_house.owner}", |
| 926 | "amount": "{$res.net_amount}" |
| 927 | } |
| 928 | ] |
| 929 | } |
| 930 | }, |
| 931 | { |
| 932 | "app": "state", |
| 933 | "state": "{ |
| 934 | response['message'] = 'Bought shortcode'; |
| 935 | response['event'] = $res.event; |
| 936 | var['house_'||$seller_house_num] = $res.seller_house; |
| 937 | var['house_'||$my_house_num] = $res.buyer_house; |
| 938 | }" |
| 939 | } |
| 940 | ] |
| 941 | }, |
| 942 | { |
| 943 | "if": "{trigger.data.edit_plot AND $plot_num AND trigger.data.info}", |
| 944 | "init": "{ |
| 945 | require($plot.owner == trigger.address, "you are not the owner"); |
| 946 | }", |
| 947 | "messages": [ |
| 948 | { |
| 949 | "app": "state", |
| 950 | "state": "{ |
| 951 | $plot.info = trigger.data.info; |
| 952 | response['message'] = 'Edited plot'; |
| 953 | response['event'] = json_stringify({type: 'edit_plot', plot_num: $plot_num, info: trigger.data.info}); |
| 954 | var['plot_'||$plot_num] = $plot; |
| 955 | }" |
| 956 | } |
| 957 | ] |
| 958 | }, |
| 959 | { |
| 960 | "if": "{trigger.data.edit_user AND (trigger.data.info OR trigger.data.main_plot_num)}", |
| 961 | "messages": [ |
| 962 | { |
| 963 | "app": "state", |
| 964 | "state": "{ |
| 965 | if (trigger.data.main_plot_num){ |
| 966 | $main_plot = var['plot_'||trigger.data.main_plot_num]; |
| 967 | require($main_plot, "no such plot"); |
| 968 | require($main_plot.owner == trigger.address, "you are not the owner"); |
| 969 | var['user_main_plot_'||$main_plot.city||'_'||trigger.address] = trigger.data.main_plot_num; |
| 970 | } |
| 971 | if (trigger.data.info) |
| 972 | var['user_'||trigger.address] = trigger.data.info; |
| 973 | response['message'] = 'Edited user profile'; |
| 974 | response['event'] = json_stringify({type: 'edit_user', address: trigger.address, info: trigger.data.info, main_plot_num: trigger.data.main_plot_num}); |
| 975 | }" |
| 976 | } |
| 977 | ] |
| 978 | }, |
| 979 | { |
| 980 | "if": "{trigger.data.withdraw_fundraise AND trigger.data.amount AND trigger.address == $fundraise_recipient}", |
| 981 | "messages": [ |
| 982 | { |
| 983 | "app": "payment", |
| 984 | "payload": { |
| 985 | "asset": "base", |
| 986 | "outputs": [ |
| 987 | { |
| 988 | "address": "{trigger.address}", |
| 989 | "amount": "{trigger.data.amount}" |
| 990 | } |
| 991 | ] |
| 992 | } |
| 993 | } |
| 994 | ] |
| 995 | } |
| 996 | ] |
| 997 | } |
| 998 | } |
| 999 | ] |