Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zora constraint #611

Merged
merged 1 commit into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/constraints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
BrightIDAuraVerification,
BrightIDMeetVerification,
)
from core.constraints.captcha import HasVerifiedCloudflareCaptcha
from core.constraints.EAS import Attest, BeAttestedBy
from core.constraints.ens import HasENSVerification
from core.constraints.farcaster import (
Expand Down Expand Up @@ -63,7 +64,7 @@
IsFollowingTwitterBatch,
IsFollowinTwitterUser,
)
from core.constraints.captcha import HasVerifiedCloudflareCaptcha
from core.constraints.zora import DidMintZoraNFT


def get_constraint(constraint_label: str) -> ConstraintVerification:
Expand Down
4 changes: 3 additions & 1 deletion core/constraints/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ConstraintApp(Enum):
MUON = "muon"
OPTIMISM = "optimism"
OCTANT = "octant"
ZORA = "zora"

@classmethod
def choices(cls):
Expand Down Expand Up @@ -59,9 +60,10 @@ class ConstraintVerification(ABC):
invalid_cache_until = 60
valid_cache_until = 60 * 60

def __init__(self, user_profile) -> None:
def __init__(self, user_profile, *, obj=None) -> None:
self.user_profile = user_profile
self._param_values = {}
self.obj = obj

def get_info(self, *args, **kwargs):
pass
Expand Down
2 changes: 1 addition & 1 deletion core/constraints/arbitrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def has_bridged(self, from_time=None):
}

res = subgraph.send_post_request(
subgraph.path.get("arb_bridge_mainnet"), query=query, vars=vars
subgraph.paths.get("arb_bridge_mainnet"), query=query, vars=vars
)
match res:
case None:
Expand Down
33 changes: 33 additions & 0 deletions core/constraints/zora.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from datetime import datetime

from core.constraints.abstract import (
ConstraintApp,
ConstraintParam,
ConstraintVerification,
)
from core.thirdpartyapp import ZoraUtil


class DidMintZoraNFT(ConstraintVerification):
app_name = ConstraintApp.ZORA.value
_param_keys = [ConstraintParam.ADDRESS]

def __init__(self, user_profile) -> None:
super().__init__(user_profile)

def is_observed(self, *args, **kwargs) -> bool:
zora_util = ZoraUtil()
user_addresses = self.user_addresses
nft_address = self.param_values[ConstraintParam.ADDRESS.name]
for address in user_addresses:
res = zora_util.get_tx(nft_address=nft_address, address=address)
if res is None:
continue
for tx in res.values:
if (
tx.get("method") == "mint"
and datetime.strptime(tx.get("timestamp"), "%Y-%m-%dT%H:%M:%S.%fZ")
> self.obj.start_at
):
return True
return False
5 changes: 4 additions & 1 deletion core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
from core.constraints.captcha import HasVerifiedCloudflareCaptcha
from encrypted_model_fields.fields import EncryptedCharField
from rest_framework.exceptions import ValidationError
from solders.keypair import Keypair
from solders.pubkey import Pubkey

from core.constraints.captcha import HasVerifiedCloudflareCaptcha

from .constraints import (
AllowListVerification,
Attest,
Expand All @@ -28,6 +29,7 @@
DidDelegateArbToAddress,
DidDelegateOPToAddress,
DidLikedFarcasterCast,
DidMintZoraNFT,
DidMirrorOnLensPublication,
DidQuoteTweet,
DidRecastFarcasterCast,
Expand Down Expand Up @@ -157,6 +159,7 @@ class Type(models.TextChoices):
IsFollowingTwitterBatch,
IsFollowingFarcasterBatch,
HasVerifiedCloudflareCaptcha,
DidMintZoraNFT,
]

name = models.CharField(
Expand Down
1 change: 1 addition & 0 deletions core/thirdpartyapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from .lens import LensUtil # noqa: F401
from .subgraph import Subgraph
from .twitter import RapidTwitter, TwitterUtils
from .zora import ZoraUtil
1 change: 1 addition & 0 deletions core/thirdpartyapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
),
}
SUBGRAPH_BASE_URL = os.getenv("SUBGRAPH_BASE_URL", "https://api.studio.thegraph.com")
ZORA_BASE_URL = os.getenv("ZORA_BASE_URL", "https://explorer.zora.energy")
4 changes: 2 additions & 2 deletions core/thirdpartyapp/subgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class Subgraph:
requests = RequestHelper(config.SUBGRAPH_BASE_URL)
path = {
paths = {
"unitap_pass": "query/73675/unitap-pass-eth/version/latest",
"arb_bridge_mainnet": "query/21879/unitap-arb-bridge-mainnet/version/latest",
}
Expand Down Expand Up @@ -53,7 +53,7 @@ def get_unitap_pass_holders(
while True:
vars["skip"] = count
res = self.send_post_request(
self.path.get("unitap_pass"), query=query, vars=vars
self.paths.get("unitap_pass"), query=query, vars=vars
)
match res:
case None:
Expand Down
36 changes: 36 additions & 0 deletions core/thirdpartyapp/zora.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import logging

from core.request_helper import RequestException, RequestHelper
from core.thirdpartyapp import config


class ZoraUtil:
request = RequestHelper(base_url=config.ZORA_BASE_URL)
paths = {"get-address-token-transfer": "api/v2/addresses/{address}/token-transfers"}

def __init__(self) -> None:
self.session = self.request.get_session()

@property
def headers(self):
return {"accept: application/json"}

def get_tx(self, nft_address: str, address: str):
params = {
"type": "ERC-20,ERC-721,ERC-1155",
"filter": "to",
"token": nft_address,
}
try:
res = self.request.get(
path=self.paths.get("get-address-token-transfer").format(
address=address
),
session=self.session,
headers=self.headers,
params=params,
)
return res.json()
except RequestException as e:
logging.error(f"Could not get token info from zora: {str(e)}")
return None
29 changes: 29 additions & 0 deletions prizetap/migrations/0077_alter_constraint_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.0.4 on 2024-08-28 16:19

from django.db import migrations, models

def create_prizetap_constraint(apps, schema_editor):
Constraint = apps.get_model("prizetap", "Constraint")

Constraint.objects.create(
name="core.DidMintZoraNFT",
description="DidMintZoraNFT",
title="Did Mint Zora NFT",
type="VER",
)


class Migration(migrations.Migration):

dependencies = [
('prizetap', '0076_alter_constraint_name'),
]

operations = [
migrations.AlterField(
model_name='constraint',
name='name',
field=models.CharField(choices=[('core.BrightIDMeetVerification', 'BrightIDMeetVerification'), ('core.BrightIDAuraVerification', 'BrightIDAuraVerification'), ('core.HasNFTVerification', 'HasNFTVerification'), ('core.HasTokenVerification', 'HasTokenVerification'), ('core.HasTokenTransferVerification', 'HasTokenTransferVerification'), ('core.AllowListVerification', 'AllowListVerification'), ('core.HasENSVerification', 'HasENSVerification'), ('core.HasLensProfile', 'HasLensProfile'), ('core.IsFollowingLensUser', 'IsFollowingLensUser'), ('core.BeFollowedByLensUser', 'BeFollowedByLensUser'), ('core.DidMirrorOnLensPublication', 'DidMirrorOnLensPublication'), ('core.DidCollectLensPublication', 'DidCollectLensPublication'), ('core.HasMinimumLensPost', 'HasMinimumLensPost'), ('core.HasMinimumLensFollower', 'HasMinimumLensFollower'), ('core.BeFollowedByFarcasterUser', 'BeFollowedByFarcasterUser'), ('core.HasMinimumFarcasterFollower', 'HasMinimumFarcasterFollower'), ('core.DidLikedFarcasterCast', 'DidLikedFarcasterCast'), ('core.DidRecastFarcasterCast', 'DidRecastFarcasterCast'), ('core.IsFollowingFarcasterUser', 'IsFollowingFarcasterUser'), ('core.HasFarcasterProfile', 'HasFarcasterProfile'), ('core.BeAttestedBy', 'BeAttestedBy'), ('core.Attest', 'Attest'), ('core.HasDonatedOnGitcoin', 'HasDonatedOnGitcoin'), ('core.HasMinimumHumanityScore', 'HasMinimumHumanityScore'), ('core.HasGitcoinPassportProfile', 'HasGitcoinPassportProfile'), ('core.IsFollowingFarcasterChannel', 'IsFollowingFarcasterChannel'), ('core.BridgeEthToArb', 'BridgeEthToArb'), ('core.IsFollowinTwitterUser', 'IsFollowinTwitterUser'), ('core.BeFollowedByTwitterUser', 'BeFollowedByTwitterUser'), ('core.DidRetweetTweet', 'DidRetweetTweet'), ('core.DidQuoteTweet', 'DidQuoteTweet'), ('core.HasMuonNode', 'HasMuonNode'), ('core.DelegateArb', 'DelegateArb'), ('core.DelegateOP', 'DelegateOP'), ('core.DidDelegateArbToAddress', 'DidDelegateArbToAddress'), ('core.DidDelegateOPToAddress', 'DidDelegateOPToAddress'), ('core.GLMStakingVerification', 'GLMStakingVerification'), ('core.IsFollowingTwitterBatch', 'IsFollowingTwitterBatch'), ('core.IsFollowingFarcasterBatch', 'IsFollowingFarcasterBatch'), ('core.HasVerifiedCloudflareCaptcha', 'HasVerifiedCloudflareCaptcha'), ('core.DidMintZoraNFT', 'DidMintZoraNFT'), ('prizetap.HaveUnitapPass', 'HaveUnitapPass'), ('prizetap.NotHaveUnitapPass', 'NotHaveUnitapPass'), ('faucet.OptimismDonationConstraint', 'OptimismDonationConstraint'), ('faucet.OptimismClaimingGasConstraint', 'OptimismClaimingGasConstraint')], max_length=255, unique=True),
),
migrations.RunPython(create_prizetap_constraint),
]
11 changes: 7 additions & 4 deletions prizetap/validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import time

from django.core.cache import cache
from rest_framework.exceptions import PermissionDenied, ValidationError
Expand Down Expand Up @@ -31,7 +30,7 @@ def check_user_constraints(self, raise_exception=True):
result = dict()
for c in self.raffle.constraints.all():
constraint: ConstraintVerification = get_constraint(c.name)(
self.user_profile
self.user_profile, obj=self.raffle
)
constraint.response = c.response
try:
Expand All @@ -57,11 +56,15 @@ def check_user_constraints(self, raise_exception=True):
)
else:
is_verified = constraint.is_observed(
**cdata, from_time=int(self.raffle.start_at.timestamp()), context={"request": self.request}
**cdata,
from_time=int(self.raffle.start_at.timestamp()),
context={"request": self.request},
)

if constraint.is_cachable:
constraint_data = cache_constraint_result(cache_key, is_verified, constraint, info)
constraint_data = cache_constraint_result(
cache_key, is_verified, constraint, info
)
else:
constraint_data = {"is_verified": is_verified, "info": info}

Expand Down
28 changes: 28 additions & 0 deletions tokenTap/migrations/0063_alter_constraint_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.0.4 on 2024-08-28 16:19

from django.db import migrations, models

def create_tokentap_constraint(apps, schema_editor):
Constraint = apps.get_model("tokenTap", "Constraint")

Constraint.objects.create(
name="core.DidMintZoraNFT",
description="DidMintZoraNFT",
title="Did Mint Zora NFT",
type="VER",
)

class Migration(migrations.Migration):

dependencies = [
('tokenTap', '0062_alter_constraint_name'),
]

operations = [
migrations.AlterField(
model_name='constraint',
name='name',
field=models.CharField(choices=[('core.BrightIDMeetVerification', 'BrightIDMeetVerification'), ('core.BrightIDAuraVerification', 'BrightIDAuraVerification'), ('core.HasNFTVerification', 'HasNFTVerification'), ('core.HasTokenVerification', 'HasTokenVerification'), ('core.HasTokenTransferVerification', 'HasTokenTransferVerification'), ('core.AllowListVerification', 'AllowListVerification'), ('core.HasENSVerification', 'HasENSVerification'), ('core.HasLensProfile', 'HasLensProfile'), ('core.IsFollowingLensUser', 'IsFollowingLensUser'), ('core.BeFollowedByLensUser', 'BeFollowedByLensUser'), ('core.DidMirrorOnLensPublication', 'DidMirrorOnLensPublication'), ('core.DidCollectLensPublication', 'DidCollectLensPublication'), ('core.HasMinimumLensPost', 'HasMinimumLensPost'), ('core.HasMinimumLensFollower', 'HasMinimumLensFollower'), ('core.BeFollowedByFarcasterUser', 'BeFollowedByFarcasterUser'), ('core.HasMinimumFarcasterFollower', 'HasMinimumFarcasterFollower'), ('core.DidLikedFarcasterCast', 'DidLikedFarcasterCast'), ('core.DidRecastFarcasterCast', 'DidRecastFarcasterCast'), ('core.IsFollowingFarcasterUser', 'IsFollowingFarcasterUser'), ('core.HasFarcasterProfile', 'HasFarcasterProfile'), ('core.BeAttestedBy', 'BeAttestedBy'), ('core.Attest', 'Attest'), ('core.HasDonatedOnGitcoin', 'HasDonatedOnGitcoin'), ('core.HasMinimumHumanityScore', 'HasMinimumHumanityScore'), ('core.HasGitcoinPassportProfile', 'HasGitcoinPassportProfile'), ('core.IsFollowingFarcasterChannel', 'IsFollowingFarcasterChannel'), ('core.BridgeEthToArb', 'BridgeEthToArb'), ('core.IsFollowinTwitterUser', 'IsFollowinTwitterUser'), ('core.BeFollowedByTwitterUser', 'BeFollowedByTwitterUser'), ('core.DidRetweetTweet', 'DidRetweetTweet'), ('core.DidQuoteTweet', 'DidQuoteTweet'), ('core.HasMuonNode', 'HasMuonNode'), ('core.DelegateArb', 'DelegateArb'), ('core.DelegateOP', 'DelegateOP'), ('core.DidDelegateArbToAddress', 'DidDelegateArbToAddress'), ('core.DidDelegateOPToAddress', 'DidDelegateOPToAddress'), ('core.GLMStakingVerification', 'GLMStakingVerification'), ('core.IsFollowingTwitterBatch', 'IsFollowingTwitterBatch'), ('core.IsFollowingFarcasterBatch', 'IsFollowingFarcasterBatch'), ('core.HasVerifiedCloudflareCaptcha', 'HasVerifiedCloudflareCaptcha'), ('core.DidMintZoraNFT', 'DidMintZoraNFT'), ('tokenTap.OncePerMonthVerification', 'OncePerMonthVerification'), ('tokenTap.OnceInALifeTimeVerification', 'OnceInALifeTimeVerification'), ('faucet.OptimismHasClaimedGasConstraint', 'OptimismHasClaimedGasConstraint')], max_length=255, unique=True),
),
migrations.RunPython(create_tokentap_constraint),
]
16 changes: 7 additions & 9 deletions tokenTap/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from .models import ClaimReceipt, TokenDistribution




class SetDistributionTxValidator:
def __init__(self, *args, **kwargs):
self.user_profile: UserProfile = kwargs["user_profile"]
Expand All @@ -38,8 +36,6 @@ def is_valid(self, data):
raise PermissionDenied("Tx hash is not valid")




class TokenDistributionValidator:
def __init__(
self,
Expand All @@ -64,14 +60,14 @@ def check_user_permissions(self, raise_exception=True):
result = dict()
for c in self.td.constraints.all():
constraint: ConstraintVerification = get_constraint(c.name)(
self.user_profile,
self.user_profile, obj=self.td
)
constraint.response = c.response
try:
constraint.param_values = param_values[c.name]
except KeyError:
pass

cdata = self.td_data.get(str(c.pk), dict())
cache_key = f"tokentap-{self.user_profile.pk}-{self.td.pk}-{c.pk}"
constraint_data = cache.get(cache_key)
Expand All @@ -89,11 +85,13 @@ def check_user_permissions(self, raise_exception=True):
is_verified = constraint.is_observed(
**cdata,
token_distribution=self.td,
context={"request": self.request}
context={"request": self.request},
)

if constraint.is_cachable:
constraint_data = cache_constraint_result(cache_key, is_verified, constraint, info)
constraint_data = cache_constraint_result(
cache_key, is_verified, constraint, info
)
else:
constraint_data = {"is_verified": is_verified, "info": info}

Expand All @@ -103,7 +101,7 @@ def check_user_permissions(self, raise_exception=True):
if len(error_messages) and raise_exception:
raise PermissionDenied(error_messages)
return result

def cache_constraint(self):
pass

Expand Down
Loading