[
    "autonomous agent",
    {
        "doc_url": "https://ostable.org/stable-oswap-oswap-arb.json",
        "init": "{
        $min_reserve_delta = params.min_reserve_delta OTHERWISE 1e5;
        $stable_aa = params.stable_aa;
        $stable_params = definition[$stable_aa][1].params;
        $curve_aa = $stable_params.curve_aa;
        $curve_params = definition[$curve_aa][1].params;
        $m = $curve_params.m;
        $n = $curve_params.n;
        $decimals1 = $curve_params.decimals1;
        $decimals2 = $curve_params.decimals2;
        $reserve_asset_decimals = $curve_params.reserve_asset_decimals;
        // tokens
        $interest_asset = var[$curve_aa]['asset2'];
        $stable_asset = var[$stable_aa]['asset'];
        $reserve_asset = $curve_params.reserve_asset OTHERWISE 'base';
        $reserve_oswap_aa = params.reserve_oswap_aa;
        $stable_oswap_aa = params.stable_oswap_aa;
        $stable_oswap_params = definition[$stable_oswap_aa][1].params;
        $reserve_oswap_params = definition[$reserve_oswap_aa][1].params;
        
        $stable_oswap_fee = $stable_oswap_params.swap_fee / 1e11;
        $reserve_oswap_fee = $reserve_oswap_params.swap_fee / 1e11;
        $oswap_net = (1-$stable_oswap_fee)*(1-$reserve_oswap_fee);
        $stable_imported_asset = $stable_oswap_params.asset0 == $stable_asset ? $stable_oswap_params.asset1 : $stable_oswap_params.asset0;
        $stable_imported_asset2 = $reserve_oswap_params.asset0 == $reserve_asset ? $reserve_oswap_params.asset1 : $reserve_oswap_params.asset0;
        if ($stable_imported_asset2 != $stable_imported_asset)
            bounce("stable imported asset mismatch " || $stable_imported_asset || ' != ' || $stable_imported_asset2);
        $get_oracles = () => {
            $oracles = var[$curve_aa]['oracles'];
            if ($oracles)
                return $oracles;
            $initial_oracles = [];
            if ($curve_params.oracle1 AND $curve_params.feed_name1)
                $initial_oracles[] = {oracle: $curve_params.oracle1, feed_name: $curve_params.feed_name1, op: $curve_params.op1 OTHERWISE '*'};
            if ($curve_params.oracle2 AND $curve_params.feed_name2)
                $initial_oracles[] = {oracle: $curve_params.oracle2, feed_name: $curve_params.feed_name2, op: $curve_params.op2 OTHERWISE '*'};
            if ($curve_params.oracle3 AND $curve_params.feed_name3)
                $initial_oracles[] = {oracle: $curve_params.oracle3, feed_name: $curve_params.feed_name3, op: $curve_params.op3 OTHERWISE '*'};
            $initial_oracles
        };
        $get_oracle_price = () => {
            $oracles = $get_oracles();
            $oracle_price = reduce($oracles, 3, ($price, $oracle_info) => {
                if (!exists($price))
                    return false;
                $df = data_feed[[oracles=$oracle_info.oracle, feed_name=$oracle_info.feed_name, ifnone=false]];
                if (!exists($df))
                    return false;
                ($oracle_info.op == '*') ? $price * $df : $price / $df
            }, 1);
            $oracle_price
        };
        $get_leverage = () => $curve_params.leverage OTHERWISE 0;
        $get_growth_factor = () => {
            $interest_rate = var[$curve_aa]['interest_rate'];
            $term = (timestamp - var[$curve_aa]['rate_update_ts']) / (360 * 24 * 3600); // in years
            $growth_factor = var[$curve_aa]['growth_factor'] * (1 + $interest_rate)^$term;
            $growth_factor
        };
        $g = $get_growth_factor();
        $get_target_p2 = () => {
            $oracle_price = $get_oracle_price();
            if (!exists($oracle_price))
                return false;
            $target_p2 = $oracle_price^($get_leverage() - 1) * $g;
            $target_p2
        };
        $get_reserve = ($s1, $s2) => {
            $r = $s1^$m * $s2^$n;
            $r
        };
        $get_p2 = ($s1, $s2) => {
            $p2 = $s1^$m * $n * $s2^($n-1); // derivative
            $p2
        };
        $get_slow_capacity_share = () => {
            $slow_capacity_share_var = var[$curve_aa]['slow_capacity_share'];
            if (exists($slow_capacity_share_var))
                $slow_capacity_share = $slow_capacity_share_var;
            else if (exists($curve_params.slow_capacity_share))
                $slow_capacity_share = $curve_params.slow_capacity_share;
            else
                $slow_capacity_share = 0.5;
            $slow_capacity_share
        };
        $get_distance = ($p2, $target_p2) => (exists($p2) AND exists($target_p2)) ? abs($p2 - $target_p2) / min($p2, $target_p2) : 0;
        $fee_multiplier = var[$curve_aa]['fee_multiplier'] OTHERWISE $curve_params.fee_multiplier OTHERWISE 5;
        $get_fee = ($avg_reserve, $old_distance, $new_distance) => {
            $fee = ceil($fee_multiplier * $avg_reserve * ($new_distance - $old_distance) * ($new_distance + $old_distance));
            $fee
        };
        $get_reserve_needed = ($tokens1, $tokens2) => {
            $slow_capacity_share = $get_slow_capacity_share();
            $fast_capacity_share = 1 - $slow_capacity_share;
            $initial_p2 = var[$curve_aa]['p2'];
            $target_p2 = $get_target_p2();
            $distance = $get_distance($initial_p2, $target_p2);
            $reserve = var[$curve_aa]['reserve'];
            if (!$reserve AND ($tokens1 <= 0 OR $tokens2 <= 0))
                bounce("initial mint must be with both tokens");
            $new_supply1 = var[$curve_aa]['supply1'] + $tokens1;
            $new_supply2 = var[$curve_aa]['supply2'] + $tokens2;
            $s1 = $new_supply1 / 10^$decimals1;
            $s2 = $new_supply2 / 10^$decimals2;
            $r = $get_reserve($s1, $s2);
            $p2 = $get_p2($s1, $s2);
            $new_reserve = ceil($r * 10^$reserve_asset_decimals);
            $reserve_delta = $new_reserve - $reserve; // can be negative
            if ($tokens1 >= 0 AND $tokens2 >= 0 AND $reserve_delta < 0)
                bounce("issuing tokens while the reserve decreases?");
            if ($tokens1 <= 0 AND $tokens2 <= 0 AND $reserve_delta > 0)
                bounce("burning tokens while the reserve increases?");
            $new_distance = $get_distance($p2, $target_p2);
            $avg_reserve = ($reserve + $new_reserve) / 2;
            $fast_capacity = var[$curve_aa]['fast_capacity'];
            if ($distance == 0 AND $new_distance == 0){
                $fee = 0;
                $reward = 0;
                $reserve_needed = $reserve_delta;
            }
            else if ($new_distance >= $distance){ // going away from the target price - pay a fee
                $reward = 0;
                $regular_fee = $get_fee($avg_reserve, $distance, $new_distance);
                $new_fast_capacity = $fast_capacity + $regular_fee * $fast_capacity_share;
                $distance_share = 1 - $distance/$new_distance;
                // reward that would be paid for returning the price back to $initial_p2
                $reverse_reward = $distance_share * $new_fast_capacity;
                if ($regular_fee >= $reverse_reward)
                    $fee = $regular_fee;
                else
                    $fee = ceil($distance_share / (1 - $distance_share * $fast_capacity_share) * $fast_capacity);
                $reserve_needed = $reserve_delta + $fee; // negative for payouts
            }
            else { // going towards the target price - get a reward
                $fee = 0;
                $regular_reward = floor((1 - $new_distance/$distance) * $fast_capacity);
                if ($curve_params.capped_reward){
                    // if the reward would be greater than the fee for the reverse transaction, cap the reward by the fee
                    $reverse_fee = $get_fee($avg_reserve, $new_distance, $distance);
                    $reward = min($regular_reward, $reverse_fee);
                }
                else
                    $reward = $regular_reward;
                $reserve_needed = $reserve_delta - $reward; // negative for payouts
            }
            $reserve_needed
        };
        $aa2aa_bytes = 2000;
        $network_fee = ($reserve_asset == 'base') ? 4000 : 0; // for fees and pinging the DE
        $full_network_fee = $network_fee + ($reserve_asset == 'base' ?  $aa2aa_bytes : 0);
    }",
        "messages": {
            "cases": [
                {
                    "if": "{ trigger.data.arb }",
                    "init": "{
                    $s2 = var[$curve_aa]['supply2'] / 10^$decimals2;
                    $p2 = var[$curve_aa]['p2'];
                    $fc = var[$curve_aa]['fast_capacity'] / 10^$reserve_asset_decimals;
                    $target_p2 = $get_target_p2();
                    if ($p2 >= $target_p2)
                        bounce("p2 is above the peg");
                    $reward_share = ($n-1)*$fc/($target_p2-$p2)/$s2;
                    $fee_share = 2 * $fee_multiplier * ($n-1)/$n * ($target_p2-$p2)/$p2;
                    $s = balance[$stable_oswap_aa][$stable_asset] / 10^$decimals2;
                    $i1 = balance[$stable_oswap_aa][$stable_imported_asset];
                    $i2 = balance[$reserve_oswap_aa][$stable_imported_asset];
                    $ro = balance[$reserve_oswap_aa][$reserve_asset] / 10^$reserve_asset_decimals;
                    $p_stable = $p2/$g;
                    $p_oswap = ($i1/$s) / ($i2/$ro);
                    // profitable to buy on ostable and sell on oswap
                    if ($p_stable * (1-$reward_share) < $p_oswap * $oswap_net){
                        $delta_s = (sqrt($g*$ro*$i2/$i1*$s*$oswap_net / $p2 / (1 - ($n-1)*$fc/$s2/($target_p2-$p2))) - $i2/$i1*$s) / (1 + $i2/$i1);
                        $delta_s2 = $delta_s/$g;
                        if ($delta_s2 < 0)
                            bounce("expected to buy T2, calc says should sell " || $delta_s2);
                        $tokens2 = round($delta_s2 * 10^$decimals2);
                        if ($tokens2 == 0)
                            bounce("0 T2");
                        $reserve_needed = $get_reserve_needed(0, $tokens2);
                        if ($reserve_needed < $min_reserve_delta)
                            bounce("reserve amount too small " || $reserve_needed);
                        $amount = $reserve_needed + $full_network_fee;
                        $from = 'curve';
                    }
                    else if ($p_stable * (1-$fee_share) > $p_oswap / $oswap_net){
                        $delta_s = (sqrt($g*$ro*$i2/$i1*$s/$oswap_net / $p2 / (1 - 2*$fee_multiplier*($n-1)/$n^2*($target_p2-$p2)/$p2)) - $i2/$i1*$s) / (1 + $i2/$i1);
                        $delta_s2 = $delta_s/$g;
                        if ($delta_s2 > 0)
                            bounce("expected to sell T2, calc says should buy " || $delta_s2);
                        $delta_ro = $ro/(1+$i2/$i1+$i2/$i1*$s/$delta_s) / $oswap_net;
                        if ($delta_ro > 0)
                            bounce("delta_ro > 0: " || $delta_s2);
                        $amount = round(-$delta_ro * 10^$reserve_asset_decimals);
                        if ($amount < $min_reserve_delta)
                            bounce("amount too small " || $amount);
                        $from = 'oswap';
                    }
                    else
                        bounce("no arb opportunity exists");
                }",
                    "messages": [
                        {
                            "app": "payment",
                            "payload": {
                                "asset": "{$reserve_asset}",
                                "outputs": [
                                    {
                                        "address": "{$from == 'curve' ? $curve_aa : $reserve_oswap_aa}",
                                        "amount": "{ $amount }"
                                    }
                                ]
                            }
                        },
                        {
                            "if": "{$from == 'curve'}",
                            "app": "data",
                            "payload": {
                                "tokens2": "{$tokens2}",
                                "tokens2_to": "{$stable_aa}"
                            }
                        },
                        {
                            "if": "{$from == 'oswap'}",
                            "app": "data",
                            "payload": {
                                "to_aa": "{$stable_oswap_aa}",
                                "to": "{this_address}"
                            }
                        },
                        {
                            "app": "state",
                            "state": "{
                            var['sent_amount'] = $amount;
                            response['message'] = $from == 'curve' ? 'will arb by buying from the curve' : 'will arb by selling to the curve';
                        }"
                        }
                    ]
                },
                {
                    "if": "{ trigger.output[[asset=$stable_asset]] > 0 AND trigger.address == $stable_aa }",
                    "init": "{
                    if (!var['sent_amount'])
                        bounce('no sent amount when received from stable AA');
                }",
                    "messages": [
                        {
                            "app": "payment",
                            "payload": {
                                "asset": "{$stable_asset}",
                                "outputs": [
                                    {
                                        "address": "{$stable_oswap_aa}",
                                        "amount": "{ trigger.output[[asset=$stable_asset]] }"
                                    }
                                ]
                            }
                        },
                        {
                            "app": "data",
                            "payload": {
                                "to_aa": "{$reserve_oswap_aa}",
                                "to": "{this_address}"
                            }
                        }
                    ]
                },
                {
                    "if": "{ trigger.output[[asset=$stable_asset]] > 0 AND trigger.address == $stable_oswap_aa }",
                    "init": "{
                    if (!var['sent_amount'])
                        bounce('no sent amount when received from oswap');
                }",
                    "messages": [
                        {
                            "app": "payment",
                            "payload": {
                                "asset": "{$stable_asset}",
                                "outputs": [
                                    {
                                        "address": "{$stable_aa}",
                                        "amount": "{ trigger.output[[asset=$stable_asset]] }"
                                    }
                                ]
                            }
                        },
                        {
                            "app": "data",
                            "payload": {
                                "to": "{$curve_aa}"
                            }
                        }
                    ]
                },
                {
                    "if": "{ trigger.output[[asset=$reserve_asset]] > 0 AND (trigger.address == $reserve_oswap_aa OR trigger.address == $curve_aa) }",
                    "messages": [
                        {
                            "app": "state",
                            "state": "{
                            $sent_amount = var['sent_amount'];
                            if (!$sent_amount)
                                bounce('no sent amount');
                            if (trigger.output[[asset=$reserve_asset]] < $sent_amount)
                                bounce('unprofitable ' || trigger.output[[asset=$reserve_asset]] || ' < ' || $sent_amount);
                            var['sent_amount'] = false;
                        }"
                        }
                    ]
                },
                {
                    "if": "{ trigger.data.withdraw AND trigger.address == params.owner }",
                    "messages": [
                        {
                            "app": "payment",
                            "payload": {
                                "asset": "{trigger.data.asset OTHERWISE $reserve_asset}",
                                "outputs": [
                                    {
                                        "address": "{params.owner}",
                                        "amount": "{ trigger.data.amount OTHERWISE '' }"
                                    }
                                ]
                            }
                        }
                    ]
                },
                {
                    "if": "{ trigger.output[[asset=$reserve_asset]] > 0 }",
                    "messages": [
                        {
                            "app": "state",
                            "state": "{
                            response['message'] = 'added ' || trigger.output[[asset=$reserve_asset]];
                        }"
                        }
                    ]
                }
            ]
        }
    }
]