Definition: [
"autonomous agent",
{
"doc_url": "https://friends.obyte.org/friends.json",
"getters": "{
/* nonce: 645324 */
$matching_timeout = 600;
$year = 31536000; // 365 * 24 * 3600;
$day = 86400; // 24 * 3600;
$ghost_admin = 'WMFLGI2GLAB2MDF2KQAH37VNRRMK7A5N';
$get_deposited_supply = () => var['total_locked'];
$get_variables = () => {
var['variables'] OTHERWISE {
rewards_aa: 'TQNNRLADHTUPTJ7KEWCYS5XRSILFEKBG',
messaging_attestors: 'WMFLGI2GLAB2MDF2KQAH37VNRRMK7A5N:JBW7HT5CRBSF7J7RD26AYLQG6GZDPFPS:5KM36CFPBD2QJLVD65PHZG34WEM4RPY2',
real_name_attestors: 'FSJVTTCHUIWALPN7Y6GYEKZACXMEXIG3',
referrer_deposit_reward_share: 0.01,
followup_reward_share: 0.1,
min_balance_instead_of_real_name: 1e8,
}
};
$get_followup_reward_days = () => {
'60': true, // +2 months
'150': true, // +3 months
'270': true, // +4 months
'450': true, // +6 months
'720': true, // +9 months
'1080': true, // +12 months
'1620': true, // +18 months
};
$get_ceiling_price = () => {
$constants = var['constants'];
2^((timestamp - $constants.launch_ts)/$year)
};
$is_eligible_for_infriends = ($address) => {
$get_variables().rewards_aa#5.$is_eligible_for_infriends($address, var['user_'||$address], $get_ceiling_price(), $get_followup_reward_days())
};
$get_rewards_description = ($rewards, $address) => {
$res = {
desc: "liquid " || $rewards.user1.liquid/1e9 || " FRD, locked " || $rewards.user1.locked/1e9 || " FRD"
};
if ($rewards.user1.new_user_reward)
$res.desc = $res.desc || ", including new user reward " || $rewards.user1.new_user_reward/1e9 || " FRD";
if ($rewards.user1.referred_user_reward) // someone referred me and this is my first friendship
$res.desc = $res.desc || ", including referred user reward " || $rewards.user1.referred_user_reward/1e9 || " FRD";
if ($rewards.referrers[$address]) // referrer reward is not included in .locked, that's why "plus"
$res.desc = $res.desc || ", plus referrer reward " || $rewards.referrers[$address]/1e9 || " FRD";
$res.desc
};
$get_deposit_asset_exchange_rates_on_aa = ($deposit_asset, $aa) => {
$params = definition[$aa][1].params;
$bX = $params.x_asset == $deposit_asset AND $params.y_asset == 'base';
$bY = $params.x_asset == 'base' AND $params.y_asset == $deposit_asset;
require($bX OR $bY, "deposit asset must be one of the pool's assets and the other asset must be GBYTE");
$recent = var[$aa]['recent'];
require($recent AND $recent.current AND $recent.prev, "no recent state of the pool");
$pmax = max($recent.current.pmax, $recent.prev.pmax);
$pmin = min($recent.current.pmin, $recent.prev.pmin);
require($pmin > 0 AND $pmax > 0, "pmin and pmax must be > 0");
{min: ($bX ? $pmin : 1/$pmax) * 0.9, max: ($bX ? $pmax : 1/$pmin) * 1.1}
};
$get_deposit_asset_exchange_rates = ($deposit_asset) => {
$aa = var['deposit_asset_'||$deposit_asset];
require($aa, "unknown deposit asset");
$get_deposit_asset_exchange_rates_on_aa($deposit_asset, $aa)
};
}",
"init": "{
$followup_claim_term = 10; // days
$constants = var['constants'] OTHERWISE {};
// FRD token
$asset = $constants.asset;
$governance_aa = $constants.governance_aa;
$variables = $get_variables();
$ceiling_price = 2^((timestamp - $constants.launch_ts)/$year);
if ($asset)
$received_amount = trigger.output[[asset=$asset]];
$received_bytes_amount = max(trigger.output[[asset=base]] - 10000, 0);
$deposit_asset = trigger.data.deposit_asset;
if ($deposit_asset AND trigger.address != $governance_aa){
$received_deposit_asset_amount = trigger.output[[asset=$deposit_asset]];
$exchange_rates = $get_deposit_asset_exchange_rates($deposit_asset);
}
$governance_base_aa = 'T32TTXO7TCPN3U4S2YX6K6KTE2CY54WM';
}",
"messages": {
"cases": [
{
"if": "{ trigger.data.define AND !$asset }",
"messages": [
{
"app": "asset",
"payload": {
"is_private": false,
"is_transferrable": true,
"auto_destroy": false,
"fixed_denominations": false,
"issued_by_definer_only": true,
"cosigned_by_definer": false,
"spender_attested": false
}
},
{
"app": "definition",
"payload": {
"definition": [
"autonomous agent",
{
"base_aa": "{$governance_base_aa}",
"params": {
"friend_aa": "{this_address}"
}
}
]
}
},
{
"app": "state",
"state": "{
$constants.asset = response_unit;
$constants.governance_aa = unit[response_unit].messages[[.app='definition']].payload.address;
$constants.launch_ts = timestamp;
var['constants'] = $constants;
response['asset'] = response_unit;
}"
}
]
},
{
"if": "{ trigger.address == $governance_aa AND trigger.data.name }",
"init": "{
$name = trigger.data.name;
$value = trigger.data.value;
}",
"messages": [
{
"app": "state",
"state": "{
if ($name == 'deposit_asset'){
require(length(trigger.data.deposit_asset) == 44, "invalid deposit asset");
var['deposit_asset_'||trigger.data.deposit_asset] = $value;
}
else {
$variables[$name] = $value;
var['variables'] = $variables;
}
}"
}
]
},
{
"if": "{
$ghost_name = trigger.data.ghost_name;
trigger.address == $ghost_admin AND trigger.data.add_ghost AND $ghost_name
}",
"init": "{
require(!is_valid_address($ghost_name), "ghost_name must not be a valid address");
require(!var['user_'||$ghost_name], "this ghost already exists");
}",
"messages": [
{
"app": "state",
"state": "{
var['user_'||$ghost_name] = {
balances: {frd: 100e9}, // not included in total_locked
unlock_date: false,
reg_ts: timestamp,
ghost: true,
last_date: timestamp_to_string(timestamp, 'date'), // to make sure nobody gets new user rewards
};
response['message'] = "Added the new ghost account";
}"
}
]
},
{
"if": "{
($received_amount OR $received_bytes_amount OR $received_deposit_asset_amount OR trigger.data.term) AND trigger.data.deposit
}",
"init": "{
$username = attestation[[attestors=$variables.messaging_attestors, address=trigger.address, ifnone=false]].username;
require($username, "your address must be attested on a messaging service");
$messagingUserId = attestation[[attestors=$variables.messaging_attestors, address=trigger.address, ifnone=false]].userId OTHERWISE $username;
$m_addr = var['m_address_'||$messagingUserId];
require(!$m_addr OR $m_addr == trigger.address, "only one account per messaging user is allowed");
require(!is_aa(trigger.address), "AAs not allowed to participate");
$user = var['user_'||trigger.address] OTHERWISE {balances: {}, unlock_date: false, reg_ts: timestamp, current_ghost_num: 1};
$bNewUser = !$user.unlock_date;
$total_balance = $user.balances.frd + $received_amount + ($user.balances.base + $received_bytes_amount + ($deposit_asset ? ($user.balances[$deposit_asset] + $received_deposit_asset_amount) * $exchange_rates.min : 0))/$ceiling_price;
$bBigDeposit = $total_balance >= $variables.min_balance_instead_of_real_name;
if (!$bBigDeposit)
$user_id = attestation[[attestors=$variables.real_name_attestors, address=trigger.address, ifnone=false]].user_id;
require($user_id OR $bBigDeposit, "your address must be real-name attested or you should deposit at least "||($variables.min_balance_instead_of_real_name/1e9)||" FRD");
if ($user_id){
$addr = var['rn_address_'||$user_id];
require(!$addr OR $addr == trigger.address, "only one account per real user is allowed");
}
if (exists(trigger.data.ref)){
require(is_valid_address(trigger.data.ref), "referrer address not valid");
require(var['user_'||trigger.data.ref], "referrer doesn't exist");
if ($bNewUser){
$user.ref = trigger.data.ref;
if (trigger.data.no_referrer_deposit_reward)
$user.no_referrer_deposit_reward = true;
}
else
response['ref_ignored'] = "Referrer can be set only with the first deposit";
}
if ($user.ref){
$referrer = var['user_'||$user.ref];
$min_unlock_date = timestamp_to_string(timestamp + $day, 'date');
$bReferrerEligible = $referrer.unlock_date >= $min_unlock_date;
if (!$bReferrerEligible)
response['warning'] = "Referrer's unlock date is less than 1 day in the future and they are not eligible to receive the deposit reward";
}
$term = trigger.data.term OTHERWISE 1;
require($term >= 1, "minimum term is 1 day");
$new_unlock_date = timestamp_to_string(timestamp + $term * 24 * 3600, 'date');
if (!$bNewUser)
require($new_unlock_date >= $user.unlock_date, "new unlock date must not go back");
}",
"messages": [
{
"if": "{$user.ref AND !$user.no_referrer_deposit_reward AND $bReferrerEligible}",
"app": "payment",
"payload": {
"asset": "{$asset}",
"outputs": [
{
"address": "{$user.ref}",
"amount": "{floor(($received_amount + ($received_bytes_amount + $received_deposit_asset_amount * $exchange_rates.min)/$ceiling_price) * $variables.referrer_deposit_reward_share)}"
}
]
}
},
{
"app": "state",
"state": "{
$user.balances.frd = $user.balances.frd + $received_amount;
$user.balances.base = $user.balances.base + $received_bytes_amount;
if ($deposit_asset){
$user.balances[$deposit_asset] = $user.balances[$deposit_asset] + $received_deposit_asset_amount;
require(length($user.balances) <= 3, "maximum 3 deposit assets allowed, remove some old ones before adding new assets");
}
$user.unlock_date = $new_unlock_date;
var['user_'||trigger.address] = $user;
if ($bNewUser AND $user_id)
var['rn_address_'||$user_id] = trigger.address;
var['m_address_'||$messagingUserId] = trigger.address;
var['total_locked'] += $received_amount;
var['total_locked_bytes'] += $received_bytes_amount;
if ($received_amount OR $received_bytes_amount OR $received_deposit_asset_amount)
response['message'] = "Deposited";
response['unlock_date'] = $new_unlock_date;
response['event'] = json_stringify({type: 'deposit', owner: trigger.address, amount: $received_amount, bytes_amount: $received_bytes_amount, asset_amount: $received_deposit_asset_amount, deposit_asset: $deposit_asset});
}"
}
]
},
{
"if": "{
$friend = trigger.data.friend;
$days = trigger.data.days;
(trigger.data.connect OR trigger.data.followup AND $days) AND $friend
}",
"init": "{
$today = timestamp_to_string(timestamp, 'date');
$yesterday = timestamp_to_string(timestamp - 24 * 3600, 'date');
$min_unlock_date = timestamp_to_string(timestamp + $day, 'date');
$user1 = var['user_'||trigger.address];
require($user1, "please make a deposit first");
require($user1.unlock_date >= $min_unlock_date, "your unlock date must be "||$min_unlock_date||" or later");
require($friend != trigger.address, "you cannot become friends with yourself");
$user2 = var['user_'||$friend];
require($user2, "your friend must make a deposit first");
if ($user2.ghost){
$required_streak = ($user1.current_ghost_num + 1)^2;
require($user1.current_streak >= $required_streak, "you need a "||$required_streak||"-day streak to befriend the next ghost, your current streak length is only "||$user1.current_streak);
}
else
require($user2.unlock_date >= $min_unlock_date, "your friend's unlock date must be "||$min_unlock_date||" or later");
if (trigger.data.connect){
require(!$days, "don't send days for the initial connect");
require(!var['friend_'||trigger.address||'_'||$today], "you already made a friend today, try tomorrow");
if (!$user2.ghost){
require(!var['friend_'||$friend||'_'||$today], "your friend already made a friend today, try tomorrow");
$eligibility = $variables.rewards_aa#5.$are_eligible(trigger.address, $friend, $user1, $user2, $ceiling_price, $get_followup_reward_days());
require($eligibility.user1_eligible, "you are not eligible to make in-friends today");
require($eligibility.user2_eligible, "your friend is not eligible to make in-friends today");
}
}
else{
require($get_followup_reward_days()[$days], "no such follow-up");
}
$isAB = trigger.address < $friend AND !$user2.ghost; // alphabetically sorted, or the ghost comes first
$pair = $isAB ? trigger.address||'_'||$friend : $friend||'_'||trigger.address;
$key = 'friendship_'||$pair;
$friendship = var[$key] OTHERWISE {followup_reward_share: $variables.followup_reward_share}; // the share is fixed for all future followups
if (!trigger.data.connect){ // followup
require($friendship.initial.accept_ts, "you are not friends");
$elapsed_days = (timestamp - $friendship.initial.accept_ts)/24/3600;
require($elapsed_days >= +$days, "too early");
require($elapsed_days <= $days + $followup_claim_term, "too late");
}
$rewards = $variables.rewards_aa#20.$get_rewards(trigger.address, $friend, $user1, $user2, $ceiling_price, this_address, !trigger.data.connect);
if (!trigger.data.connect){ // followup, multiply all rewards by followup_reward_share
$rewards.user1.locked = floor($rewards.user1.locked * $friendship.followup_reward_share);
$rewards.user1.liquid = floor($rewards.user1.liquid * $friendship.followup_reward_share);
$rewards.user2.locked = floor($rewards.user2.locked * $friendship.followup_reward_share);
$rewards.user2.liquid = floor($rewards.user2.liquid * $friendship.followup_reward_share);
}
$rewards_description = $get_rewards_description($rewards, trigger.address);
$index = trigger.data.connect ? 'initial' : 'followup_'||$days;
if ($user2.ghost)
$friendship[$index] = {first: $friend, ts: timestamp};
if (!$friendship[$index]){
$friendship[$index] = {first: trigger.address, ts: timestamp};
response['message'] = "Registered your request. Your friend must send their request within 10 minutes, otherwise you both will have to start over. Expected rewards: "||$rewards_description||".";
}
else{
require(!$friendship[$index].accept_ts, trigger.data.connect ? "you are already friends" : "already paid");
if ($friendship[$index].first == trigger.address){
$friendship[$index].ts = timestamp;
response['message'] = "Refreshed your request. Your friend must send their request within 10 minutes, otherwise you both will have to start over. Expected rewards: "||$rewards_description||".";
}
else{
$bInTime = timestamp < $friendship[$index].ts + $matching_timeout;
if (!$bInTime){
$friendship[$index].ts = timestamp;
$friendship[$index].first = trigger.address;
response['message'] = "Unfortunately, you are too late. Your friend has to send their request again within 10 minutes, otherwise you both will have to start over. Expected rewards: "||$rewards_description||".";
}
else{ // pay rewards
$friendship[$index].accept_ts = timestamp;
$user1.balances.frd = $user1.balances.frd + $rewards.user1.locked;
$user2.balances.frd = $user2.balances.frd + $rewards.user2.locked;
$user1.locked_rewards = $user1.locked_rewards + $rewards.user1.locked;
$user2.locked_rewards = $user2.locked_rewards + $rewards.user2.locked;
$user1.liquid_rewards = $user1.liquid_rewards + $rewards.user1.liquid;
$user2.liquid_rewards = $user2.liquid_rewards + $rewards.user2.liquid;
if (trigger.data.connect){
$bContinueStreak1 = $user1.last_date AND $user1.last_date == $yesterday;
$bContinueStreak2 = $user2.last_date AND $user2.last_date == $yesterday;
$user1.total_streak = $bContinueStreak1 ? $user1.total_streak + 1 : 1;
$user2.total_streak = $bContinueStreak2 ? $user2.total_streak + 1 : 1;
$user1.current_streak = $bContinueStreak1 ? $user1.current_streak + 1 : 1;
$user2.current_streak = $bContinueStreak2 ? $user2.current_streak + 1 : 1;
if ($user2.ghost){
$user1.current_streak = 0; // reset the streak
$user1.current_ghost_num = $user1.current_ghost_num + 1;
}
$user1.last_date = $today;
$user2.last_date = $today;
if ($rewards.user1.is_new) // record a new user reward for the opposite user
$user2.new_user_rewards = $user2.new_user_rewards + $rewards.user2.new_user_reward;
if ($rewards.user2.is_new) // record a new user reward for the opposite user
$user1.new_user_rewards = $user1.new_user_rewards + $rewards.user1.new_user_reward;
}
response['message'] = (trigger.data.connect ? "Now you've become friends and you've received the following rewards: " : "You've received followup rewards: ") || $rewards_description || ".";
response['events'] = json_stringify({type: 'rewards', user1: trigger.address, user2: $friend, rewards: $rewards, followup: !trigger.data.connect, days: $days, ghost: $user2.ghost});
}
}
}
}",
"messages": [
{
"if": "{$friendship[$index].accept_ts}",
"app": "payment",
"payload": {
"asset": "{$asset}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$rewards.user1.liquid}"
},
{
"address": "{$friend}",
"amount": "{$rewards.user2.liquid}",
"if": "{!$user2.ghost}"
}
]
}
},
{
"app": "state",
"state": "{
if ($friendship[$index].accept_ts){
var['user_'||trigger.address] = $user1;
var['user_'||$friend] = $user2;
if (trigger.data.connect){
var['friend_'||trigger.address||'_'||$today] = $friend;
var['friend_'||$friend||'_'||$today] = trigger.address;
var['total_new_user_rewards'] += $rewards.user1.new_user_reward + $rewards.user2.new_user_reward;
if ($rewards.referrers){
$referrer_totals = {total: 0};
foreach($rewards.referrers, 2, ($addr, $reward) => {
$user = var['user_'||$addr]; // it can be one of the connecting users whose var has already been updated
$user.balances.frd = $user.balances.frd + $reward;
$user.referral_rewards = $user.referral_rewards + $reward;
$user.locked_rewards = $user.locked_rewards + $reward;
var['user_'||$addr] = $user;
$referrer_totals.total = $referrer_totals.total + $reward;
});
var['total_referral_rewards'] += 2 * $referrer_totals.total; // the same reward goes to the referred user
}
}
// users' locked rewards include the referred rewards but not the referrer rewards
var['total_locked'] += $rewards.user1.locked + ($user2.ghost ? 0 : $rewards.user2.locked) + $referrer_totals.total;
// replace user1 and user2 with alphabetically sorted a and b
$rewards.a = $isAB ? $rewards.user1 : $rewards.user2;
$rewards.b = $isAB ? $rewards.user2 : $rewards.user1;
delete($rewards, 'user1');
delete($rewards, 'user2');
$friendship[$index].rewards = $rewards;
delete($friendship[$index], 'first'); // save storage space to make sure we don't exceed 1024 bytes
delete($friendship[$index], 'ts');
}
var[$key] = $friendship;
}"
}
]
},
{
"if": "{trigger.data.withdraw}",
"init": "{
$user = var['user_'||trigger.address];
require($user, "you are not a user");
require(timestamp_to_string(timestamp, 'date') >= $user.unlock_date, "your balance unlocks on "||$user.unlock_date);
require($user.balances.frd > 0 OR $user.balances.base > 0 OR $deposit_asset AND $user.balances[$deposit_asset] > 0, "you have no balance");
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$user.balances.base}"
},
{
"address": "{$governance_aa}",
"amount": 1000
}
]
}
},
{
"app": "payment",
"payload": {
"asset": "{$asset}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$user.balances.frd}"
}
]
}
},
{
"if": "{$deposit_asset}",
"app": "payment",
"payload": {
"asset": "{$deposit_asset}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$user.balances[$deposit_asset]}"
}
]
}
},
{
"app": "data",
"payload": {
"update_user_balance": 1,
"address": "{trigger.address}"
}
},
{
"app": "state",
"state": "{
response['message'] = 'Withdrawn';
response['event'] = json_stringify({type: 'withdrawal', address: trigger.address, balance: $user.balances.frd, bytes_balance: $user.balances.base, deposit_asset_balance: $deposit_asset ? $user.balances[$deposit_asset] : 0, deposit_asset: $deposit_asset});
var['total_locked'] -= $user.balances.frd;
var['total_locked_bytes'] -= $user.balances.base;
$user.balances.frd = 0;
$user.balances.base = 0;
if ($deposit_asset)
$user.balances[$deposit_asset] = 0;
var['user_'||trigger.address] = $user;
}"
}
]
},
{
"if": "{trigger.data.replace AND ($received_amount OR $received_bytes_amount OR $received_deposit_asset_amount)}",
"init": "{
require(($received_amount?1:0) + ($received_bytes_amount?1:0) + ($received_deposit_asset_amount?1:0) == 1, "don't send more than one token");
$user = var['user_'||trigger.address];
require($user, "you are not a user");
$out_amounts = {base: 0, frd: 0, deposit_asset: 0};
if ($deposit_asset){
if ($received_deposit_asset_amount){
$out_amounts.frd = floor($received_deposit_asset_amount * $exchange_rates.min / $ceiling_price);
}
else if ($received_amount){
$out_amounts.deposit_asset = floor($received_amount * $ceiling_price / $exchange_rates.max);
require($user.balances[$deposit_asset] >= $out_amounts.deposit_asset, "not enough deposit asset locked");
}
else
bounce("bytes are not swapped for deposit assets");
}
else if ($received_amount){
$out_amounts.base = floor($received_amount * $ceiling_price);
require($user.balances.base >= $out_amounts.base, "not enough bytes locked");
}
else if ($received_bytes_amount){ // received bytes
$out_amounts.frd = floor($received_bytes_amount / $ceiling_price);
}
if ($out_amounts.frd)
require($user.balances.frd >= $out_amounts.frd, "not enough FRD locked");
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$out_amounts.base}"
}
]
}
},
{
"app": "payment",
"payload": {
"asset": "{$asset}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$out_amounts.frd}"
}
]
}
},
{
"if": "{$out_amounts.deposit_asset}",
"app": "payment",
"payload": {
"asset": "{$deposit_asset}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$out_amounts.deposit_asset}"
}
]
}
},
{
"app": "state",
"state": "{
response['message'] = 'Replaced';
response['event'] = json_stringify({type: 'replace', address: trigger.address, received_amount: $received_amount, received_bytes_amount: $received_bytes_amount, received_deposit_asset_amount: $received_deposit_asset_amount, out_amount: $out_amounts.frd, out_bytes_amount: $out_amounts.base, out_deposit_asset_amount: $out_amounts.deposit_asset, deposit_asset: $deposit_asset});
var['total_locked'] += $received_amount - $out_amounts.frd;
var['total_locked_bytes'] += $received_bytes_amount - $out_amounts.base;
$user.balances.frd = $user.balances.frd + $received_amount - $out_amounts.frd;
$user.balances.base = $user.balances.base + $received_bytes_amount - $out_amounts.base;
if ($deposit_asset){
$user.balances[$deposit_asset] = $user.balances[$deposit_asset] + $received_deposit_asset_amount - $out_amounts.deposit_asset;
if ($user.balances[$deposit_asset] == 0)
delete($user.balances, $deposit_asset);
require(length($user.balances) <= 3, "maximum 3 deposit assets allowed, remove some old ones before adding new assets");
}
var['user_'||trigger.address] = $user;
}"
}
]
}
]
}
}
]