ETH Price: $3,399.37 (-3.41%)
Gas: 8 Gwei

Contract

0xBBA872aA6dD946C83674d96999F4E9C78afD81c2
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Value

There are no matching entries

Please try again later

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To Value
187197872023-12-05 11:16:35196 days ago1701774995  Contract Creation0 ETH
Loading...
Loading

Minimal Proxy Contract for 0xcb1bd8f8ae14beb58e647d0641bba816dba446e7

Contract Name:
Vyper_contract

Compiler Version
vyper:0.3.9

Optimization Enabled:
N/A

Other Settings:
None license

Contract Source Code (Vyper language format)

# @version 0.3.9

# Interfaces

from vyper.interfaces import ERC20 as IERC20
from vyper.interfaces import ERC721 as IERC721

interface IDelegationRegistry:
    def getHotWallet(cold_wallet: address) -> address: view
    def setHotWallet(hot_wallet_address: address, expiration_timestamp: uint256, lock_hot_wallet_address: bool): nonpayable
    def setExpirationTimestamp(expiration_timestamp: uint256): nonpayable


# Events


# Structs

struct Rental:
    id: bytes32 # keccak256 of the renter, token_id, start and expiration
    owner: address
    renter: address
    token_id: uint256
    start: uint256
    min_expiration: uint256
    expiration: uint256
    amount: uint256


struct Listing:
    token_id: uint256
    price: uint256 # price per hour, 0 means not listed
    min_duration: uint256 # min duration in hours
    max_duration: uint256 # max duration in hours, 0 means unlimited


# Global Variables

is_initialised: public(bool)
owner: public(address)
caller: public(address)
listing: public(Listing)
active_rental: public(Rental)
unclaimed_rewards: public(uint256)

payment_token_addr: public(address)
nft_contract_addr: public(address)
delegation_registry_addr: public(address)


##### EXTERNAL METHODS - WRITE #####

@external
def initialise(
    owner: address,
    payment_token_addr: address,
    nft_contract_addr: address,
    delegation_registry_addr: address
):
    assert not self.is_initialised, "already initialised"

    if self.caller != empty(address):
        assert msg.sender == self.caller, "not caller"
    else:
        self.caller = msg.sender

    self.owner = owner
    self.is_initialised = True

    self.payment_token_addr = payment_token_addr
    self.nft_contract_addr = nft_contract_addr
    self.delegation_registry_addr = delegation_registry_addr


@external
def deposit(token_id: uint256, price: uint256, min_duration: uint256, max_duration: uint256):
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert IERC721(self.nft_contract_addr).ownerOf(token_id) == self.owner, "not owner of token"
    assert IERC721(self.nft_contract_addr).getApproved(token_id) == self, "not approved for token"

    if max_duration != 0 and min_duration > max_duration:
        raise "min duration > max duration"

    self.listing = Listing({
        token_id: token_id,
        price: price,
        min_duration: min_duration,
        max_duration: max_duration
    })

    # transfer token to this contract
    IERC721(self.nft_contract_addr).safeTransferFrom(self.owner, self, token_id, b"")

    # create delegation
    self._delegate_to_owner()


@external
def set_listing(sender: address, price: uint256, min_duration: uint256, max_duration: uint256):
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert sender == self.owner, "not owner of vault"

    self._set_listing(sender, price, min_duration, max_duration)


@external
def set_listing_and_delegate_to_owner(sender: address, price: uint256, min_duration: uint256, max_duration: uint256):
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert sender == self.owner, "not owner of vault"
    assert self.active_rental.expiration < block.timestamp, "active rental ongoing"

    self._set_listing(sender, price, min_duration, max_duration)
    self._delegate_to_owner()



@external
def start_rental(renter: address, expiration: uint256) -> Rental:
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert self._is_active(), "listing does not exist"
    assert self.active_rental.expiration < block.timestamp, "active rental ongoing"
    assert self._is_within_duration_range(block.timestamp, expiration), "duration not respected"

    listing: Listing = self.listing

    rental_amount: uint256 = self._compute_rental_amount(block.timestamp, expiration, listing.price)
    assert IERC20(self.payment_token_addr).allowance(renter, self) >= rental_amount, "insufficient allowance"

    # transfer rental amount from renter to this contract
    assert IERC20(self.payment_token_addr).transferFrom(renter, self, rental_amount), "transferFrom failed"

    # create delegation
    if IDelegationRegistry(self.delegation_registry_addr).getHotWallet(self) == renter:
        IDelegationRegistry(self.delegation_registry_addr).setExpirationTimestamp(expiration)
    else:
        IDelegationRegistry(self.delegation_registry_addr).setHotWallet(renter, expiration, False)

    # store unclaimed rewards
    self._consolidate_claims()

    # create rental
    rental_id: bytes32 = self._compute_rental_id(renter, listing.token_id, block.timestamp, expiration)
    self.active_rental = Rental({
        id: rental_id,
        owner: self.owner,
        renter: renter,
        token_id: listing.token_id,
        start: block.timestamp,
        min_expiration: block.timestamp + listing.min_duration * 3600,
        expiration: expiration,
        amount: rental_amount
    })

    return self.active_rental


@external
def close_rental(sender: address) -> (Rental, uint256):
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"

    rental: Rental = self.active_rental

    assert rental.expiration >= block.timestamp, "active rental does not exist"
    assert sender == rental.renter, "not renter of active rental"

    # compute amount to send back to renter
    real_expiration_adjusted: uint256 = block.timestamp
    if block.timestamp < rental.min_expiration:
        real_expiration_adjusted = rental.min_expiration
    pro_rata_rental_amount: uint256 = self._compute_real_rental_amount(
        rental.expiration - rental.start,
        real_expiration_adjusted - rental.start,
        rental.amount
    )
    payback_amount: uint256 = rental.amount - pro_rata_rental_amount

    # clear active rental
    rental.expiration = block.timestamp
    rental.amount = 0
    self.active_rental = rental

    # set unclaimed rewards
    self.unclaimed_rewards += pro_rata_rental_amount

    # revoke delegation
    IDelegationRegistry(self.delegation_registry_addr).setHotWallet(empty(address), 0, False)

    # transfer unused payment to renter
    assert IERC20(self.payment_token_addr).transfer(rental.renter, payback_amount), "transfer failed"

    return rental, pro_rata_rental_amount


@external
def claim(sender: address) -> uint256:
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert sender == self.owner, "not owner of vault"
    assert self._claimable_rewards() > 0, "no rewards to claim"

    # consolidate last renting rewards if existing
    self._consolidate_claims()

    rewards_to_claim: uint256 = self.unclaimed_rewards

    # clear uncclaimed rewards
    self.unclaimed_rewards = 0

    # transfer reward to nft owner
    assert IERC20(self.payment_token_addr).transfer(self.active_rental.owner, rewards_to_claim), "transfer failed"

    return rewards_to_claim


@external
def withdraw(sender: address) -> uint256:
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert sender == self.owner, "not owner of vault"
    assert self.active_rental.expiration < block.timestamp, "active rental ongoing"

    # consolidate last renting rewards if existing
    self._consolidate_claims()

    rewards_to_claim: uint256 = self.unclaimed_rewards
    token_id: uint256 = self.listing.token_id
    owner: address = self.owner

    # clear vault
    self.unclaimed_rewards = 0
    self.listing = empty(Listing)
    self.active_rental = empty(Rental)
    self.is_initialised = False
    self.owner = empty(address)

    # transfer token to owner
    IERC721(self.nft_contract_addr).safeTransferFrom(self, owner, token_id, b"")

    # transfer unclaimed rewards to owner
    if rewards_to_claim > 0:
        assert IERC20(self.payment_token_addr).transfer(owner, rewards_to_claim), "transfer failed"

    return rewards_to_claim


@external
def delegate_to_owner(sender: address):
    assert self.is_initialised, "not initialised"
    assert msg.sender == self.caller, "not caller"
    assert sender == self.owner, "not owner of vault"
    assert self.active_rental.expiration < block.timestamp, "active rental ongoing"

    self._delegate_to_owner()




##### INTERNAL METHODS #####

@internal
def _is_active() -> bool:
    return self.listing.price > 0

@internal
def _consolidate_claims():
    if self.active_rental.expiration < block.timestamp:
        self.unclaimed_rewards += self.active_rental.amount
        self.active_rental.amount = 0

@internal
def _is_within_duration_range(start: uint256, expiration: uint256) -> bool:
    return expiration - start >= self.listing.min_duration * 3600 and (self.listing.max_duration == 0 or expiration - start <= self.listing.max_duration * 3600)


@pure
@internal
def _compute_rental_id(renter: address, token_id: uint256, start: uint256, expiration: uint256) -> bytes32:
    return keccak256(concat(convert(renter, bytes32), convert(token_id, bytes32), convert(start, bytes32), convert(expiration, bytes32)))


@pure
@internal
def _compute_rental_amount(start: uint256, expiration: uint256, price: uint256) -> uint256:
    return (expiration - start) * price / 3600


@pure
@internal
def _compute_real_rental_amount(duration: uint256, real_duration: uint256, rental_amount: uint256) -> uint256:
    return rental_amount * real_duration / duration


@view
@internal
def _claimable_rewards() -> uint256:
    if self.active_rental.expiration < block.timestamp:
        return self.unclaimed_rewards + self.active_rental.amount
    else:
        return self.unclaimed_rewards

@internal
def _delegate_to_owner():
    delegation_registry: IDelegationRegistry = IDelegationRegistry(self.delegation_registry_addr)
    owner: address = self.owner
    if delegation_registry.getHotWallet(self) != owner:
        delegation_registry.setHotWallet(owner, max_value(uint256), False)


@internal
def _set_listing(sender: address, price: uint256, min_duration: uint256, max_duration: uint256):
    if max_duration != 0 and min_duration > max_duration:
        raise "min duration > max duration"

    self.listing.price = price
    self.listing.min_duration = min_duration
    self.listing.max_duration = max_duration



##### EXTERNAL METHODS - VIEW #####

@view
@external
def claimable_rewards() -> uint256:
    return self._claimable_rewards()


@view
@external
def onERC721Received(_operator: address, _from: address, _tokenId: uint256, _data: Bytes[1024]) -> bytes4:
    return method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4)

Contract ABI

[{"stateMutability":"nonpayable","type":"function","name":"initialise","inputs":[{"name":"owner","type":"address"},{"name":"payment_token_addr","type":"address"},{"name":"nft_contract_addr","type":"address"},{"name":"delegation_registry_addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit","inputs":[{"name":"token_id","type":"uint256"},{"name":"price","type":"uint256"},{"name":"min_duration","type":"uint256"},{"name":"max_duration","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_listing","inputs":[{"name":"sender","type":"address"},{"name":"price","type":"uint256"},{"name":"min_duration","type":"uint256"},{"name":"max_duration","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_listing_and_delegate_to_owner","inputs":[{"name":"sender","type":"address"},{"name":"price","type":"uint256"},{"name":"min_duration","type":"uint256"},{"name":"max_duration","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"start_rental","inputs":[{"name":"renter","type":"address"},{"name":"expiration","type":"uint256"}],"outputs":[{"name":"","type":"tuple","components":[{"name":"id","type":"bytes32"},{"name":"owner","type":"address"},{"name":"renter","type":"address"},{"name":"token_id","type":"uint256"},{"name":"start","type":"uint256"},{"name":"min_expiration","type":"uint256"},{"name":"expiration","type":"uint256"},{"name":"amount","type":"uint256"}]}]},{"stateMutability":"nonpayable","type":"function","name":"close_rental","inputs":[{"name":"sender","type":"address"}],"outputs":[{"name":"","type":"tuple","components":[{"name":"id","type":"bytes32"},{"name":"owner","type":"address"},{"name":"renter","type":"address"},{"name":"token_id","type":"uint256"},{"name":"start","type":"uint256"},{"name":"min_expiration","type":"uint256"},{"name":"expiration","type":"uint256"},{"name":"amount","type":"uint256"}]},{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"claim","inputs":[{"name":"sender","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"withdraw","inputs":[{"name":"sender","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"delegate_to_owner","inputs":[{"name":"sender","type":"address"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"claimable_rewards","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"onERC721Received","inputs":[{"name":"_operator","type":"address"},{"name":"_from","type":"address"},{"name":"_tokenId","type":"uint256"},{"name":"_data","type":"bytes"}],"outputs":[{"name":"","type":"bytes4"}]},{"stateMutability":"view","type":"function","name":"is_initialised","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"caller","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"listing","inputs":[],"outputs":[{"name":"","type":"tuple","components":[{"name":"token_id","type":"uint256"},{"name":"price","type":"uint256"},{"name":"min_duration","type":"uint256"},{"name":"max_duration","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"active_rental","inputs":[],"outputs":[{"name":"","type":"tuple","components":[{"name":"id","type":"bytes32"},{"name":"owner","type":"address"},{"name":"renter","type":"address"},{"name":"token_id","type":"uint256"},{"name":"start","type":"uint256"},{"name":"min_expiration","type":"uint256"},{"name":"expiration","type":"uint256"},{"name":"amount","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"unclaimed_rewards","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"payment_token_addr","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"nft_contract_addr","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"delegation_registry_addr","inputs":[],"outputs":[{"name":"","type":"address"}]}]

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.