Type
CONTRACT
Validation date
2023-11-28 09:57:29 UTC
Fee
0 UCO

Code (3.6 KB)

@version 1

condition triggered_by: transaction, on: add_pool(token1_address, token2_address, pool_creation_address), as: [
  type: "transfer",
  content: (
    valid? = false

    token1_address = String.to_uppercase(token1_address)
    token2_address = String.to_uppercase(token2_address)

    pool_exists? = get_pool_addresses(token1_address, token2_address) != nil

    if token1_address != token2_address && !pool_exists? do
      # Could create a new function Chain.get_transactions(list_of_address)
      pool_transaction = Chain.get_transaction(pool_creation_address)

      # Ensure tokens exists and lp token definition is good
      token1_symbol = get_token_symbol(token1_address)
      token2_symbol = get_token_symbol(token2_address)

      valid_definition? = false
      if token1_symbol != nil && token2_symbol != nil && pool_transaction != nil && pool_transaction.type == "token" do
        expected_content = get_lp_token_definition(token1_symbol, token2_symbol)

        valid_definition? = Json.parse(pool_transaction.content) == Json.parse(expected_content)
      end

      valid_code? = false
      pool_genesis_address = nil
      if valid_definition? do
        # Ensure code is valid
        pool_genesis_address = Chain.get_genesis_address(pool_creation_address)
        expected_code = get_pool_code(token1_address, token2_address, pool_genesis_address, pool_creation_address)

        valid_code? = Code.is_same?(pool_transaction.code, expected_code)
      end

      if valid_code? do
        # Ensure liquidity is provided to the pool
        valid? = List.in?(transaction.recipients, pool_genesis_address)
      end
      
      log("valid_definition? #{valid_definition?}")
      log("valid_code? #{valid_code?}")
      log("valid? #{valid?}")

    end

    valid?
  )
]

actions triggered_by: transaction, on: add_pool(token1_address, token2_address, pool_creation_address) do
  token1_address = String.to_uppercase(token1_address)
  token2_address = String.to_uppercase(token2_address)

  pool_genesis_address = Chain.get_genesis_address(pool_creation_address)
  pool_id = get_pool_id(token1_address, token2_address)
  pools = State.get("pools", Map.new())

  pool_data = [
    address: pool_genesis_address,
    lp_token_address: pool_creation_address
  ]

  pools = Map.set(pools, pool_id, pool_data)

  State.set("pools", pools)
end

condition triggered_by: transaction, on: update_code(new_code), as: [
  previous_public_key: (
    # Router code can only be updated from the master chain of the dex

    # Transaction is not yet validated so we need to use previous address
    # to get the genesis address
    previous_address = Chain.get_previous_address()
    Chain.get_genesis_address(previous_address) == 0x0000339c1c65f0b2eb77af8ab68a38cbc6d2f6828b9ad3e90bb2e7fce3a99a428aff
  ),
  code: Code.is_valid?(new_code)
]

actions triggered_by: transaction, on: update_code(new_code) do
  Contract.set_type("contract")
  Contract.set_code(new_code)
end

condition triggered_by: transaction, on: update_pools_code(), as: [
  previous_public_key: (
    # Pool code update can only be requested from the master chain of the dex

    # Transaction is not yet validated so we need to use previous address
    # to get the genesis address
    previous_address = Chain.get_previous_address()
    Chain.get_genesis_address(previous_address) == 0x0000339c1c65f0b2eb77af8ab68a38cbc6d2f6828b9ad3e90bb2e7fce3a99a428aff
  )
]

actions triggered_by: transaction, on: update_pools_code() do
  pools = State.get("pools", Map.new())

  if Map.size(pools) > 0 do
    for pool_info in Map.values(pools) do
      Contract.add_recipient address: pool_info.address, action: "update_code", args: []
    end

    Contract.set_type("transfer")
  end
end

fun get_pool_id(token1_address, token2_address) do
  if token1_address > token2_address do
    temp = token1_address
    token1_address = token2_address
    token2_address = temp
  end

  "#{token1_address}/#{token2_address}"
end

fun get_token_symbol(token_address) do
  if token_address == "UCO" do
    "UCO"
  else
    tx = Chain.get_transaction(token_address)
    # Transaction must have type token
    # Token must by fungible
    symbol = nil
    if tx != nil && tx.type == "token" do
      token_definition = Json.parse(tx.content)
      if token_definition.type == "fungible" do
        symbol = Map.get(token_definition, "symbol", nil)
      end
    end
    symbol
  end
end

export fun get_pool_addresses(token1_address, token2_address) do
  token1_address = String.to_uppercase(token1_address)
  token2_address = String.to_uppercase(token2_address)

  if token1_address > token2_address do
    temp = token1_address
    token1_address = token2_address
    token2_address = temp
  end

  pool_id = "#{token1_address}/#{token2_address}"

  pools = State.get("pools", Map.new())

  Map.get(pools, pool_id, nil)
end

export fun get_pool_list() do
  pools = State.get("pools", Map.new())

  list = []
  for pool_id in Map.keys(pools) do
    pool = Map.get(pools, pool_id)
    pool = Map.set(pool, "tokens", pool_id)
    list = List.prepend(list, pool)
  end
  list
end

export fun get_pool_code(token1_address, token2_address, pool_address, lp_token_address) do
  code = ""

  token1_address = String.to_uppercase(token1_address)
  token2_address = String.to_uppercase(token2_address)
  pool_address = String.to_uppercase(pool_address)
  lp_token_address = String.to_uppercase(lp_token_address)

  if token1_address != token2_address do
    if token1_address > token2_address do
      temp = token1_address
      token1_address = token2_address
      token2_address = temp
    end

    code = 
"""
@version 1

condition triggered_by: transaction, on: add_liquidity(token1_min_amount, token2_min_amount), as: [
  token_transfers: (
    valid_amounts? = false
    valid_liquidity? = false

    user_amounts = get_user_transfers_amount(transaction)

    if user_transfers.token1 > 0 && user_transfers.token2 > 0 do
      lp_token_supply = State.get("lp_token_supply", 0)
      reserves = State.get("reserves", [token1: 0, token2: 0])

      final_amounts = nil
      if lp_token_supply != 0 do
        # Returns final_amounts.token1 == 0 in case of insufficient funds
        final_amounts = get_final_amounts(user_amounts, reserves, token1_min_amount, token2_min_amount)
      else
        final_amounts = [token1: user_amounts.token1, token2: user_amounts.token2]
      end

      if final_amounts.token1 != 0 do
        valid_amounts? = true

        pool_balances = get_pool_balances()
        # Amount = final amounts + potential current balance over reserve
        token1_amount = final_amounts.token1 + (pool_balances.token1 - reserves.token1)
        token2_amount = final_amounts.token2 + (pool_balances.token2 - reserves.token2)

        lp_token_to_mint = get_lp_token_to_mint(token1_amount, token2_amount)

        valid_liquidity? = lp_token_to_mint > 0
      end
    end

    valid_amounts? && valid_liquidity?
  )
]

actions triggered_by: transaction, on: add_liquidity(token1_min_amount, token2_min_amount) do
  pool_balances = get_pool_balances()
  user_amounts = get_user_transfers_amount(transaction)

  lp_token_supply = State.get("lp_token_supply", 0)
  reserves = State.get("reserves", [token1: 0, token2: 0])

  final_amounts = get_final_amounts(user_amounts, reserves, token1_min_amount, token2_min_amount)
  token1_to_refund = user_amounts.token1 - final_amounts.token1
  token2_to_refund = user_amounts.token2 - final_amounts.token2

  token1_amount = pool_balances.token1 - reserves.token1 - token1_to_refund
  token2_amount = pool_balances.token2 - reserves.token2 - token2_to_refund

  lp_token_to_mint = get_lp_token_to_mint(token1_amount, token2_amount)
  lp_token_to_mint_bigint = Math.trunc(lp_token_to_mint * 100_000_000)

  # Remove minimum liquidity if this is the first liquidity if the pool
  # First liquidity minted and burned on pool creation
  if lp_token_supply == 0 do
    lp_token_to_mint_bigint = lp_token_to_mint_bigint - 10
  end

  token_specification = [
    aeip: [8, 18, 19],
    supply: lp_token_to_mint_bigint,
    token_reference: 0x#{lp_token_address},
    recipients: [
      [to: transaction.address, amount: lp_token_to_mint_bigint]
    ]
  ]

  new_token1_reserve = pool_balances.token1 - token1_to_refund
  new_token2_reserve = pool_balances.token2 - token2_to_refund

  State.set("lp_token_supply", lp_token_supply + lp_token_to_mint)
  State.set("reserves", [token1: new_token1_reserve, token2: new_token2_reserve])

  if token1_to_refund > 0 do
    Contract.add_token_transfer(to: transaction.address, amount: token1_to_refund, token_address: "#{token1_address}")
  end

  if token2_to_refund > 0 do
    if "#{token2_address}" == "UCO" do
      Contract.add_uco_transfer(to: transaction.address, amount: token2_to_refund)
    else
      Contract.add_token_transfer(to: transaction.address, amount: token2_to_refund, token_address: "#{token2_address}")
    end
  end

  Contract.set_type("token")
  Contract.set_content(Json.to_string(token_specification))
end

condition triggered_by: transaction, on: remove_liquidity(), as: [
  token_transfers: (
    valid? = false

    user_amount = get_user_lp_amount(transaction.token_transfers)
    lp_token_supply = State.get("lp_token_supply", 0)

    if user_amount > 0 && lp_token_supply > 0 do
      pool_balances = get_pool_balances()

      token1_to_remove = (user_amount * pool_balances.token1) / lp_token_supply
      token2_to_remove = (user_amount * pool_balances.token2) / lp_token_supply

      valid? = token1_to_remove > 0 && token2_to_remove > 0
    end

    valid?
  )
]

actions triggered_by: transaction, on: remove_liquidity() do
  user_amount = get_user_lp_amount(transaction.token_transfers)
  pool_balances = get_pool_balances()

  lp_token_supply = State.get("lp_token_supply")

  token1_to_remove = (user_amount * pool_balances.token1) / lp_token_supply
  token2_to_remove = (user_amount * pool_balances.token2) / lp_token_supply

  new_token1_reserve = pool_balances.token1 - token1_to_remove
  new_token2_reserve = pool_balances.token2 - token2_to_remove

  State.set("lp_token_supply", lp_token_supply - user_amount)
  State.set("reserves", [token1: new_token1_reserve, token2: new_token2_reserve])

  Contract.set_type("transfer")
  Contract.add_token_transfer(to: transaction.address, amount: token1_to_remove, token_address: "#{token1_address}")
  if "#{token2_address}" == "UCO" do
    Contract.add_uco_transfer(to: transaction.address, amount: token2_to_remove)
  else
    Contract.add_token_transfer(to: transaction.address, amount: token2_to_remove, token_address: "#{token2_address}")
  end
end

condition triggered_by: transaction, on: swap(min_to_receive), as: [
  token_transfers: (
    valid? = false

    transfer = get_user_transfer(transaction)
    if transfer != nil do
        swap = get_swap_infos(transfer.token_address, transfer.amount)

        valid? = swap.output_amount > 0 && swap.output_amount >= min_to_receive
    end

    valid? 
  )
]

actions triggered_by: transaction, on: swap(_min_to_receive) do
  transfer = get_user_transfer(transaction)

  swap = get_swap_infos(transfer.token_address, transfer.amount)

  pool_balances = get_pool_balances()
  token_to_send = nil
  if transfer.token_address == "#{token1_address}" do
    pool_balances = Map.set(pool_balances, "token2", pool_balances.token2 - swap.output_amount)
    token_to_send = "#{token2_address}" 
  else
    pool_balances = Map.set(pool_balances, "token1", pool_balances.token1 - swap.output_amount)
    token_to_send = "#{token1_address}"
  end

  State.set("reserves", [token1: pool_balances.token1, token2: pool_balances.token2])

  Contract.set_type("transfer")
  if token_to_send == "UCO" do
    Contract.add_uco_transfer(to: transaction.address, amount: swap.output_amount)
  else
    Contract.add_token_transfer(to: transaction.address, amount: swap.output_amount, token_address: token_to_send)
  end
end

condition triggered_by: transaction, on: update_code(), as: [
  previous_public_key: (
    # Pool code can only be updated from the router contract of the dex

    # Transaction is not yet validated so we need to use previous address
    # to get the genesis address
    previous_address = Chain.get_previous_address()
    Chain.get_genesis_address(previous_address) == 0x00000dd757ac0a67fd619c1d1c400037a38cdb0471e6496807d8b850dc422e5ca3aa
  )
]

actions triggered_by: transaction, on: update_code() do
  params = [
    "#{token1_address}",
    "#{token2_address}",
    0x#{pool_address},
    0x#{lp_token_address}
  ]

  new_code = Contract.call_function(0x00000dd757ac0a67fd619c1d1c400037a38cdb0471e6496807d8b850dc422e5ca3aa, "get_pool_code", params)

  if Code.is_valid?(new_code) && !Code.is_same?(new_code, contract.code) do
    Contract.set_type("contract")
    Contract.set_code(new_code)
  end
end

export fun get_ratio(token_address) do
  reserves = State.get("reserves", [token1: 0, token2: 0])
  ratio = 0

  token_address = String.to_uppercase(token_address)

  if reserves.token1 > 0 && reserves.token2 > 0 do
    if token_address == "#{token1_address}" do
      ratio = reserves.token2 / reserves.token1
    else
      ratio = reserves.token1 / reserves.token2
    end
  end
  ratio
end

export fun get_equivalent_amount(token_address, amount) do
  reserves = State.get("reserves", [token1: 0, token2: 0])
  ratio = 0

  token_address = String.to_uppercase(token_address)

  if reserves.token1 > 0 && reserves.token2 > 0 do
    if token_address == "#{token1_address}" do
      ratio = reserves.token2 / reserves.token1
    else
      ratio = reserves.token1 / reserves.token2
    end
  end

  amount * ratio
end

export fun get_lp_token_to_mint(token1_amount, token2_amount) do
  lp_token_supply = State.get("lp_token_supply", 0)
  reserves = State.get("reserves", [token1: 0, token2: 0])

  if lp_token_supply == 0 || reserves.token1 == 0 || reserves.token2 == 0 do
    # First liquidity
    Math.sqrt(token1_amount * token2_amount)
  else
    mint_amount1 = (token1_amount * lp_token_supply) / reserves.token1
    mint_amount2 = (token2_amount * lp_token_supply) / reserves.token2

    if mint_amount1 < mint_amount2 do
      mint_amount1
    else
      mint_amount2
    end
  end
end

export fun get_swap_infos(token_address, amount) do
  output_amount = 0
  fee = 0
  price_impact = 0

  reserves = State.get("reserves", [token1: 0, token2: 0])
  token_address = String.to_uppercase(token_address)

  if reserves.token1 > 0 && reserves.token2 > 0 do
    fee = amount * 0.003
    amount_with_fee = amount - fee

    market_price = 0

    if token_address == "#{token1_address}" do
      market_price = amount_with_fee * (reserves.token2 / reserves.token1)
      amount = (amount_with_fee * reserves.token2) / (amount_with_fee + reserves.token1)
      if amount < reserves.token2 do
        output_amount = amount
      end
    else
      market_price = amount_with_fee * (reserves.token1 / reserves.token2)
      amount = (amount_with_fee * reserves.token1) / (amount_with_fee + reserves.token2)
      if amount < reserves.token1 do
        output_amount = amount
      end
    end

    if output_amount > 0 do
      price_impact = ((market_price / output_amount) - 1) * 100
    end
  end

  [
    output_amount: output_amount,
    fee: fee,
    price_impact: price_impact
  ]
end

export fun get_remove_amounts(lp_token_amount) do
  reserves = State.get("reserves", [token1: 0, token2: 0])
  lp_token_supply = State.get("lp_token_supply", 0)

  token1_to_remove = 0
  token2_to_remove = 0
  
  if lp_token_supply > 0 && lp_token_amount < lp_token_supply do
    token1_to_remove = (lp_token_amount * reserves.token1) / lp_token_supply
    token2_to_remove = (lp_token_amount * reserves.token2) / lp_token_supply
  end

  [token1: token1_to_remove, token2: token2_to_remove]
end

export fun get_pool_infos() do
  reserves = State.get("reserves", [token1: 0, token2: 0])

  [
    token1: [
      address: "#{token1_address}",
      reserve: reserves.token1
    ],
    token2: [
      address: "#{token2_address}",
      reserve: reserves.token2
    ],
    lp_token: [
      address: 0x#{lp_token_address},
      supply: State.get("lp_token_supply", 0)
    ],
    fee: 0.3
  ]
end

fun get_final_amounts(user_amounts, reserves, token1_min_amount, token2_min_amount) do
  final_token1_amount = 0
  final_token2_amount = 0

  if reserves.token1 > 0 && reserves.token2 > 0 do
    token2_ratio = reserves.token2 / reserves.token1
    token2_equivalent_amount = user_amounts.token1 * token2_ratio

    if token2_equivalent_amount <= user_amounts.token2 && token2_equivalent_amount >= token2_min_amount do
      final_token1_amount = user_amounts.token1
      final_token2_amount = token2_equivalent_amount
    else
      token1_ratio = reserves.token1 / reserves.token2
      token1_equivalent_amount = user_amounts.token2 * token1_ratio

      if token1_equivalent_amount <= user_amounts.token1 && token1_equivalent_amount >= token1_min_amount do
        final_token1_amount = token1_equivalent_amount
        final_token2_amount = user_amounts.token2
      end
    end
  else
    # No reserve
    final_token1_amount = user_amounts.token1
    final_token2_amount = user_amounts.token2
  end

  [token1: final_token1_amount, token2: final_token2_amount]
end

fun get_user_transfers_amount(tx) do
  contract_address = 0x#{pool_address}

  token1_amount = 0
  token2_amount = 0
  transfers = Map.get(tx.token_transfers, contract_address)

  uco_amount = Map.get(tx.uco_transfers, contract_address)
  if uco_amount != nil do
    transfers = List.prepend(transfers, [token_address: "UCO", amount: uco_amount])
  end

  if List.size(transfers) == 2 do
    for transfer in transfers do
      if transfer.token_address == "#{token1_address}" do
        token1_amount = transfer.amount
      end
      if transfer.token_address == "#{token2_address}" do
        token2_amount = transfer.amount
      end
    end
  end

  [token1: token1_amount, token2: token2_amount]
end

fun get_user_transfer(tx) do
  contract_address = 0x#{pool_address}

  token_transfer = nil
  transfers = Map.get(tx.token_transfers, contract_address, [])

  uco_amount = Map.get(tx.uco_transfers, contract_address)
  if uco_amount != nil do
    transfers = List.prepend(transfers, [token_address: "UCO", amount: uco_amount])
  end
  
  transfer = List.at(transfers, 0)

  tokens = [
    "#{token1_address}",
    "#{token2_address}"
  ]

  if List.size(transfers) == 1 && List.in?(tokens, transfer.token_address) do
    token_transfer = transfer
  end

  token_transfer
end

fun get_user_lp_amount(token_transfers) do
  lp_token = 0x#{lp_token_address}

  lp_amount = 0
  transfers = Map.get(token_transfers, Chain.get_burn_address(), [])

  for transfer in transfers do
    if transfer.token_address == lp_token do
      lp_amount = transfer.amount
    end
  end

  lp_amount
end

fun get_pool_balances() do
  balances = Chain.get_balance(contract.address)

  token2_balance = 0
  if "#{token2_address}" == "UCO" do
    token2_balance = balances.uco
  else
    token2_id = [token_address: "#{token2_address}", token_id: 0]
    token2_balance = Map.get(balances.tokens, token2_id, 0)
  end

  token1_id = [token_address: "#{token1_address}", token_id: 0]
  [
    token1: Map.get(balances.tokens, token1_id, 0),
    token2: token2_balance
  ]
end

"""
  end

  code
end

export fun get_lp_token_definition(token1_symbol, token2_symbol) do
  if token1_symbol > token2_symbol do
    temp = token1_symbol
    token1_symbol = token2_symbol
    token2_symbol = temp
  end

  lp_token = "lp_#{token1_symbol}_#{token2_symbol}"

  Json.to_string([
    aeip: [2, 8, 18, 19],
    supply: 10,
    type: "fungible",
    symbol: lp_token,
    name: lp_token,
    allow_mint: true,
    properties: [
      description: "LP token of AESwap"
    ],
    recipients: [
      [
        to: Chain.get_burn_address(),
        amount: 10
      ]
    ]
  ])
end

Content (0 B)

State (761 B)

{
  "pools": {
    "00001A4AB7AD0CE2B494C965C66FF2962692A5FE5ECB71B345ABB53BAD88A83A01F1/UCO": {
      "address": "0000C5618CA1340ECFAA24E7B1DD750A43E807BEB0DDDD878203716E5BEE2D68B434",
      "lp_token_address": "0000D4B08F7C754731C7B2316AC173CA7AF2567D2F351706BA9E4789E63B7283D26C"
    },
    "0000288BF6F0E12457B125DC54D2DFA4EB010BE3073CF02E10FB79B696180F55B827/UCO": {
      "address": "0000151C7079B2344874DFDE4B7B982F127A4071E36C4726346A75E78319F83DB989",
      "lp_token_address": "00005CC1FEA9F0E49CF34CB6E37230EDFB9FB8949963EFF7E3171F37BFFDE7D64A25"
    },
    "00003DF600E329199BF3EE8FBE2B8223413D70BCDD97E15089E6A74D94DE3F1173B4/UCO": {
      "address": "0000BD2E4C70AA0996096FB3F949FC011159D0314365692047C5DA2D0153B13EAC2D",
      "lp_token_address": "0000E2A41863FE1FA00F8E4C8CCA07C6735B79640D51FFD25FE35EA78C7389D1484F"
    }
  }
}
                  
Movements (0)

Ownerships (1)

  • Secret shared with 1 key

    Encoded secret

    8EB87C720951CE5EF499727DDA1E2FAD2DC8A1D917EEC296825387343E828921A528708A5B1D23FD6831509BDA1D929EDB217D7DB0950AA0A1580581

    Authorized keys

    • 00017877BCF4122095926A49489009649603AB129822A19EF9D573B8FD714911ED7F

Contract recipients (0)

Inputs (0)

Contract inputs (0)

Unspent outputs (1)

Proofs and signatures

Previous public key

0001B781F2AD8CE843931A44BCA50C8446B28C8A08453BF5F812682EA702E42268BB

Previous signature

FAEF4841A35F6EFEF3DDF6F7474E07081B44B81542C9891163038175F32571803DCF2B19660BE7C53FE3E1A57422F2E309CC676B497E888E7C6253056C7AE80F

Origin signature

3045022100C46F83F0514C6F71AABD313BC5BC02AC9086FE5B7984DDFD4F6FA6E03C10AD91022070D770B22FAEEB420B5CBE7D1E66001A96813018F6939A864106ABB7D73D1B58

Proof of work

010204B3B2A53580086B9F36919CF40ABC55904729F78BF43673E216FAC1EB2451DD1E521879C6588F0CB09B150A103A39A73E2816B5ADF51F0721348BA3A66C33023B

Proof of integrity

009D51A63F302664925F80ED2968EBD6CE33D13A420EDB970DA5B83737108FB305

Coordinator signature

1E5187F38DA512BA8A0E7DC39E3E4B3A1F5A556775CAAC54C7316E50112DBBC146510111FCC2ACF1A34D128DDEA8DBEDDC85EED331A511D00A89655FB6D37108

Validator #1 public key

00011B58ED42235461734EAF253BD97A80B92899ABCC3BE680D44B6825DD2A88A947

Validator #1 signature

A4A6E56E26408CDA393FB89B802D0DF6ECF7D6B67A8DB4AEB76FD08B1B47EFAAB3833CD6B4C7952CDC240C58D7E4814F06CD99C7AD45709A937CFF15E9019407

Validator #2 public key

0001B0A94804BF8ECC9897075C6207FF63EF4D339F57A0349888E6B77CD47DB53EF3

Validator #2 signature

F0F53CD117EE36B2B588EFC7F1C186D93580584806C039031F2365D13A3860324E5D3A1B997561F711BDC365CDED0B94B80AECFF8BBAA488881EA4A3A19EFF0F