| 1 | [ |
| 2 | "autonomous agent", |
| 3 | { |
| 4 | "init": "{ |
| 5 | $decimals = params.decimals OTHERWISE 2; |
| 6 | |
| 7 | $max_loan_value_in_underlying = params.max_loan_value_in_underlying OTHERWISE 10000; |
| 8 | $overcollateralization_ratio = params.overcollateralization_ratio OTHERWISE 1.5; |
| 9 | $liquidation_ratio = params.liquidation_ratio OTHERWISE 1.3; |
| 10 | $auction_period = params.auction_period OTHERWISE 3600; |
| 11 | $oracle = params.oracle OTHERWISE 'F4KHJUCLJKY4JV7M5F754LAJX4EB7M4N'; |
| 12 | if (params.feed_name AND !params.ma_feed_name OR !params.feed_name AND !params.ma_feed_name) |
| 13 | bounce("both or none of feed names must be specified"); |
| 14 | $feed_name = params.feed_name OTHERWISE 'GBYTE_USD'; |
| 15 | $ma_feed_name = params.ma_feed_name OTHERWISE 'GBYTE_USD_MA'; |
| 16 | $expiry_ts = parse_date(params.expiry_date); |
| 17 | $asset = var['asset']; |
| 18 | $expiry_exchange_rate = var['expiry_exchange_rate']; |
| 19 | $expired = !!$expiry_exchange_rate; |
| 20 | if ($expired) |
| 21 | $insolvent = (balance[base]/1e9 * $expiry_exchange_rate < var['circulating_supply']/10^$decimals); |
| 22 | }", |
| 23 | "messages": { |
| 24 | "cases": [ |
| 25 | { |
| 26 | "if": "{ trigger.data.define AND !$asset }", |
| 27 | "messages": [ |
| 28 | { |
| 29 | "app": "asset", |
| 30 | "payload": { |
| 31 | "is_private": false, |
| 32 | "is_transferrable": true, |
| 33 | "auto_destroy": false, |
| 34 | "fixed_denominations": false, |
| 35 | "issued_by_definer_only": true, |
| 36 | "cosigned_by_definer": false, |
| 37 | "spender_attested": false |
| 38 | } |
| 39 | }, |
| 40 | { |
| 41 | "app": "state", |
| 42 | "state": "{ |
| 43 | var['asset'] = response_unit; |
| 44 | response['asset'] = response_unit; |
| 45 | }" |
| 46 | } |
| 47 | ] |
| 48 | }, |
| 49 | { |
| 50 | "if": "{trigger.data.repay AND trigger.data.id AND $asset AND trigger.output[[asset=$asset]] > 0}", |
| 51 | "init": "{ |
| 52 | $id = trigger.data.id; |
| 53 | if (var[$id || '_owner'] != trigger.address) |
| 54 | bounce('you are not the owner'); |
| 55 | if (var[$id || '_repaid']) |
| 56 | bounce('already repaid'); |
| 57 | if (var[$id || '_winner']) |
| 58 | bounce("the loan is currently on auction"); |
| 59 | $amount = var[$id || '_amount']; |
| 60 | if (trigger.output[[asset=$asset]] < $amount) |
| 61 | bounce('you sent less than the loan amount'); |
| 62 | $change = trigger.output[[asset=$asset]] - $amount; |
| 63 | $exchange_rate = $expired ? $expiry_exchange_rate : data_feed[[oracles=$oracle, feed_name=$ma_feed_name]]; |
| 64 | $collateral = var[$id || '_collateral']; |
| 65 | $due_in_bytes = $amount/10^$decimals / $exchange_rate * 1e9; |
| 66 | $min_collateral = round($due_in_bytes * $liquidation_ratio); |
| 67 | if ($collateral < $min_collateral) |
| 68 | bounce("the loan is not sufficiently collateralized"); |
| 69 | }", |
| 70 | "messages": [ |
| 71 | { |
| 72 | "app": "payment", |
| 73 | "payload": { |
| 74 | "asset": "base", |
| 75 | "outputs": [ |
| 76 | { |
| 77 | "address": "{trigger.address}", |
| 78 | "amount": "{ var[$id || '_collateral'] }" |
| 79 | } |
| 80 | ] |
| 81 | } |
| 82 | }, |
| 83 | { |
| 84 | "app": "payment", |
| 85 | "payload": { |
| 86 | "asset": "{$asset}", |
| 87 | "outputs": [ |
| 88 | { |
| 89 | "address": "{trigger.address}", |
| 90 | "amount": "{ $change }" |
| 91 | } |
| 92 | ] |
| 93 | } |
| 94 | }, |
| 95 | { |
| 96 | "app": "state", |
| 97 | "state": "{ |
| 98 | var[$id || '_repaid'] = 1; |
| 99 | var['circulating_supply'] -= trigger.output[[asset=$asset]]; |
| 100 | response['repay_id'] = $id; |
| 101 | }" |
| 102 | } |
| 103 | ] |
| 104 | }, |
| 105 | { |
| 106 | "if": "{trigger.data.add_collateral AND trigger.data.id AND $asset AND trigger.output[[asset=base]] >= 1e5}", |
| 107 | "init": "{ |
| 108 | $id = trigger.data.id; |
| 109 | if (var[$id || '_owner'] != trigger.address) |
| 110 | bounce('you are not the owner'); |
| 111 | if (var[$id || '_repaid']) |
| 112 | bounce('already repaid'); |
| 113 | }", |
| 114 | "messages": [ |
| 115 | { |
| 116 | "app": "state", |
| 117 | "state": "{ |
| 118 | var[$id || '_collateral'] += trigger.output[[asset=base]]; |
| 119 | response['collateral'] = var[$id || '_collateral']; |
| 120 | response['id'] = $id; |
| 121 | }" |
| 122 | } |
| 123 | ] |
| 124 | }, |
| 125 | { |
| 126 | "if": "{ trigger.data.seize AND trigger.data.id AND $asset }", |
| 127 | "init": "{ |
| 128 | $id = trigger.data.id; |
| 129 | if (var[$id || '_repaid']) |
| 130 | bounce('already repaid'); |
| 131 | $amount = var[$id || '_amount']; |
| 132 | if (!$amount) |
| 133 | bounce('no such loan'); |
| 134 | $exchange_rate = $expired ? $expiry_exchange_rate : data_feed[[oracles=$oracle, feed_name=$ma_feed_name]]; |
| 135 | $collateral = var[$id || '_collateral']; |
| 136 | $due_in_bytes = $amount/10^$decimals / $exchange_rate * 1e9; |
| 137 | $min_collateral = round($due_in_bytes * $liquidation_ratio); |
| 138 | $opening_collateral = round($due_in_bytes * $overcollateralization_ratio); |
| 139 | |
| 140 | if ($collateral >= $min_collateral AND trigger.address != var[$id || '_owner']) |
| 141 | bounce("the loan is sufficiently collateralized, you can't seize it"); |
| 142 | $missing_collateral = $opening_collateral - $collateral; |
| 143 | if (trigger.output[[asset=base]] < $missing_collateral) |
| 144 | bounce('you sent less than the missing collateral'); |
| 145 | $auction_end_ts = var[$id || '_auction_end_ts']; |
| 146 | if ($auction_end_ts){ |
| 147 | if (timestamp > $auction_end_ts) |
| 148 | bounce('auction already expired'); |
| 149 | $current_winner_bid = var[$id || '_winner_bid']; |
| 150 | if (trigger.output[[asset=base]] <= $current_winner_bid) |
| 151 | bounce('your bid is less than the current winner'); |
| 152 | if (trigger.output[[asset=base]] < 1.01*$current_winner_bid) |
| 153 | bounce('your bid must be at least 1% better than the current winner'); |
| 154 | $current_winner = var[$id || '_winner']; |
| 155 | |
| 156 | $bRefundToCurrentWinner = !is_aa($current_winner); |
| 157 | } |
| 158 | }", |
| 159 | "messages": [ |
| 160 | { |
| 161 | "if": "{$bRefundToCurrentWinner}", |
| 162 | "app": "payment", |
| 163 | "payload": { |
| 164 | "asset": "base", |
| 165 | "outputs": [ |
| 166 | { |
| 167 | "address": "{$current_winner}", |
| 168 | "amount": "{ $current_winner_bid - 1000 }" |
| 169 | } |
| 170 | ] |
| 171 | } |
| 172 | }, |
| 173 | { |
| 174 | "app": "state", |
| 175 | "state": "{ |
| 176 | if ($current_winner AND !$bRefundToCurrentWinner) |
| 177 | var['balance_' || $current_winner] += $current_winner_bid; |
| 178 | var[$id || '_auction_end_ts'] = timestamp + $auction_period; |
| 179 | var[$id || '_winner'] = trigger.address; |
| 180 | var[$id || '_winner_bid'] = trigger.output[[asset=base]]; |
| 181 | response['new_bid'] = trigger.output[[asset=base]]; |
| 182 | response['id'] = $id; |
| 183 | response['auction_end_ts'] = timestamp + $auction_period; |
| 184 | }" |
| 185 | } |
| 186 | ] |
| 187 | }, |
| 188 | { |
| 189 | "if": "{ trigger.data.end_auction AND trigger.data.id AND $asset }", |
| 190 | "init": "{ |
| 191 | $id = trigger.data.id; |
| 192 | if (var[$id || '_repaid']) |
| 193 | bounce('already repaid'); |
| 194 | $auction_end_ts = var[$id || '_auction_end_ts']; |
| 195 | if (!$auction_end_ts) |
| 196 | bounce('not on auction'); |
| 197 | if (timestamp < $auction_end_ts) |
| 198 | bounce('auction still under way'); |
| 199 | $winner_bid = var[$id || '_winner_bid']; |
| 200 | $winner = var[$id || '_winner']; |
| 201 | $owner = var[$id || '_owner']; |
| 202 | $collateral = var[$id || '_collateral']; |
| 203 | $amount = var[$id || '_amount']; |
| 204 | $exchange_rate = $expired ? $expiry_exchange_rate : data_feed[[oracles=$oracle, feed_name=$ma_feed_name]]; |
| 205 | $due_in_bytes = $amount/10^$decimals / $exchange_rate * 1e9; |
| 206 | $min_collateral = round($due_in_bytes * $liquidation_ratio); |
| 207 | $bHealthy = ($collateral >= $min_collateral); |
| 208 | if ($bHealthy){ |
| 209 | $bRefundToWinner = !is_aa($winner); |
| 210 | return; |
| 211 | } |
| 212 | |
| 213 | |
| 214 | if ($winner == $owner) |
| 215 | $new_collateral = $collateral + $winner_bid; |
| 216 | else{ |
| 217 | $opening_collateral = round($due_in_bytes * $overcollateralization_ratio); |
| 218 | $new_collateral = min($opening_collateral, $collateral + $winner_bid); |
| 219 | } |
| 220 | }", |
| 221 | "messages": [ |
| 222 | { |
| 223 | "if": "{$bHealthy AND $bRefundToWinner}", |
| 224 | "app": "payment", |
| 225 | "payload": { |
| 226 | "asset": "base", |
| 227 | "outputs": [ |
| 228 | { |
| 229 | "address": "{$winner}", |
| 230 | "amount": "{ $winner_bid - 1000 }" |
| 231 | } |
| 232 | ] |
| 233 | } |
| 234 | }, |
| 235 | { |
| 236 | "app": "state", |
| 237 | "state": "{ |
| 238 | var[$id || '_auction_end_ts'] = false; |
| 239 | var[$id || '_winner'] = false; |
| 240 | var[$id || '_winner_bid'] = false; |
| 241 | if (!$bHealthy) { |
| 242 | var[$id || '_owner'] = $winner; |
| 243 | var[$id || '_collateral'] = $new_collateral; |
| 244 | response['new_owner'] = $winner; |
| 245 | response['new_collateral'] = $new_collateral; |
| 246 | response['id'] = $id; |
| 247 | } |
| 248 | else { |
| 249 | if (!$bRefundToWinner) |
| 250 | var['balance_' || $winner] += $winner_bid; |
| 251 | response['id'] = $id; |
| 252 | response['end_auction'] = 0; |
| 253 | } |
| 254 | }" |
| 255 | } |
| 256 | ] |
| 257 | }, |
| 258 | { |
| 259 | "if": "{trigger.data.withdraw}", |
| 260 | "init": "{ |
| 261 | $key = 'balance_' || trigger.address; |
| 262 | $balance = var[$key] + 0; |
| 263 | if ($balance <= 0) |
| 264 | bounce("you have no balance"); |
| 265 | if (trigger.data.to){ |
| 266 | if (!is_valid_address(trigger.data.to)) |
| 267 | bounce("invalid withdrawal address: " || trigger.data.to); |
| 268 | $address = trigger.data.to; |
| 269 | } |
| 270 | else |
| 271 | $address = trigger.address; |
| 272 | $amount = trigger.data.amount OTHERWISE $balance; |
| 273 | if ($amount > $balance) |
| 274 | bounce("withdrawal amount too large, balance: " || $balance); |
| 275 | }", |
| 276 | "messages": [ |
| 277 | { |
| 278 | "app": "payment", |
| 279 | "payload": { |
| 280 | "asset": "base", |
| 281 | "outputs": [ |
| 282 | { |
| 283 | "address": "{$address}", |
| 284 | "amount": "{$amount}" |
| 285 | } |
| 286 | ] |
| 287 | } |
| 288 | }, |
| 289 | { |
| 290 | "app": "state", |
| 291 | "state": "{ |
| 292 | var[$key] -= $amount; |
| 293 | response[$address] = -$amount; |
| 294 | }" |
| 295 | } |
| 296 | ] |
| 297 | }, |
| 298 | { |
| 299 | "if": "{ trigger.data.expire AND $asset AND !$expired AND timestamp > $expiry_ts }", |
| 300 | "messages": [ |
| 301 | { |
| 302 | "app": "state", |
| 303 | "state": "{ |
| 304 | $exchange_rate_last = data_feed[[oracles=$oracle, feed_name=$feed_name]]; |
| 305 | $exchange_rate_ma = data_feed[[oracles=$oracle, feed_name=$ma_feed_name]]; |
| 306 | if (abs($exchange_rate_last - $exchange_rate_ma) > 0.05 * $exchange_rate_ma) |
| 307 | bounce('recent price changes are too fast'); |
| 308 | if (balance[base]/1e9 * min($exchange_rate_ma, $exchange_rate_last) < var['circulating_supply']/10^$decimals * $liquidation_ratio) |
| 309 | bounce("undercollateralized"); |
| 310 | var['expiry_exchange_rate'] = $exchange_rate_last; |
| 311 | response['expiry_exchange_rate'] = $exchange_rate_last; |
| 312 | }" |
| 313 | } |
| 314 | ] |
| 315 | }, |
| 316 | { |
| 317 | "if": "{ $asset AND trigger.output[[asset=$asset]] > 0 AND !$insolvent AND $expired }", |
| 318 | "init": "{ |
| 319 | $asset_amount = trigger.output[[asset=$asset]]; |
| 320 | $underlying_amount = $amount / 10^$decimals; |
| 321 | $gb_amount = $underlying_amount / $expiry_exchange_rate; |
| 322 | $bytes_amount = round($gb_amount * 1e9); |
| 323 | }", |
| 324 | "messages": [ |
| 325 | { |
| 326 | "app": "payment", |
| 327 | "payload": { |
| 328 | "asset": "base", |
| 329 | "outputs": [ |
| 330 | { |
| 331 | "address": "{trigger.address}", |
| 332 | "amount": "{ $bytes_amount }" |
| 333 | } |
| 334 | ] |
| 335 | } |
| 336 | }, |
| 337 | { |
| 338 | "app": "state", |
| 339 | "state": "{ |
| 340 | var['circulating_supply'] -= $asset_amount; |
| 341 | }" |
| 342 | } |
| 343 | ] |
| 344 | }, |
| 345 | { |
| 346 | "if": "{ $asset AND trigger.output[[asset=base]] >= 1e5 AND $expired }", |
| 347 | "init": "{ |
| 348 | $bytes_amount = trigger.output[[asset=base]] - 1000; |
| 349 | $gb_amount = $bytes_amount / 1e9; |
| 350 | $underlying_amount = $gb_amount * $expiry_exchange_rate; |
| 351 | $asset_amount = round($underlying_amount * 10^$decimals); |
| 352 | }", |
| 353 | "messages": [ |
| 354 | { |
| 355 | "app": "payment", |
| 356 | "payload": { |
| 357 | "asset": "{$asset}", |
| 358 | "outputs": [ |
| 359 | { |
| 360 | "address": "{trigger.address}", |
| 361 | "amount": "{ $asset_amount }" |
| 362 | } |
| 363 | ] |
| 364 | } |
| 365 | }, |
| 366 | { |
| 367 | "app": "state", |
| 368 | "state": "{ |
| 369 | var['circulating_supply'] += $asset_amount; |
| 370 | }" |
| 371 | } |
| 372 | ] |
| 373 | }, |
| 374 | { |
| 375 | "if": "{ trigger.output[[asset=base]] >= 1e5 AND $asset AND !$expired }", |
| 376 | "init": "{ |
| 377 | $exchange_rate = data_feed[[oracles=$oracle, feed_name=$feed_name]]; |
| 378 | $underlying_value = trigger.output[[asset=base]] / 1e9 * $exchange_rate; |
| 379 | $loan_value = $underlying_value / $overcollateralization_ratio; |
| 380 | if ($loan_value > $max_loan_value_in_underlying) |
| 381 | bounce('loan would be too large, please split it'); |
| 382 | $loan_value_in_asset = floor($loan_value * 10^$decimals); |
| 383 | if ($loan_value_in_asset == 0) |
| 384 | bounce("loan amout would be 0"); |
| 385 | }", |
| 386 | "messages": [ |
| 387 | { |
| 388 | "app": "payment", |
| 389 | "payload": { |
| 390 | "asset": "{$asset}", |
| 391 | "outputs": [ |
| 392 | { |
| 393 | "address": "{trigger.address}", |
| 394 | "amount": "{ $loan_value_in_asset }" |
| 395 | } |
| 396 | ] |
| 397 | } |
| 398 | }, |
| 399 | { |
| 400 | "app": "state", |
| 401 | "state": "{ |
| 402 | var[response_unit || '_owner'] = trigger.address; |
| 403 | var[response_unit || '_collateral'] = trigger.output[[asset=base]]; |
| 404 | var[response_unit || '_amount'] = $loan_value_in_asset; |
| 405 | var['circulating_supply'] += $loan_value_in_asset; |
| 406 | response['amount'] = $loan_value_in_asset; |
| 407 | response['id'] = response_unit; |
| 408 | response['owner'] = trigger.address; |
| 409 | response['collateral'] = trigger.output[[asset=base]]; |
| 410 | }" |
| 411 | } |
| 412 | ] |
| 413 | } |
| 414 | ] |
| 415 | } |
| 416 | } |
| 417 | ] |