From 036d2c6d811f0252e48ffe7c4194f63100bef10d Mon Sep 17 00:00:00 2001 From: jaykayudo Date: Mon, 28 Apr 2025 12:27:58 +0100 Subject: [PATCH] feature: create offer functionality --- core/migrations/0003_listing_status.py | 17 +++++ core/models.py | 8 ++ core/serializers.py | 102 +++++++++++++++++++++++++ core/service.py | 55 ++++++++++++- core/urls.py | 1 + core/utils.py | 40 ++++++++++ core/validators.py | 13 ++++ core/views.py | 14 ++++ poetry.lock | 85 ++++++++++++++++++++- pyproject.toml | 2 +- trajectfi/settings.py | 4 + 11 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 core/migrations/0003_listing_status.py create mode 100644 core/validators.py diff --git a/core/migrations/0003_listing_status.py b/core/migrations/0003_listing_status.py new file mode 100644 index 0000000..8eafa71 --- /dev/null +++ b/core/migrations/0003_listing_status.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2025-04-28 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0002_acceptednft_acceptedtoken_listing_loan_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="listing", + name="status", + field=models.IntegerField(choices=[(1, "OPEN"), (2, "CLOSED")], default=1), + ), + ] diff --git a/core/models.py b/core/models.py index 11ced49..a6592dc 100644 --- a/core/models.py +++ b/core/models.py @@ -20,6 +20,11 @@ class LoanRenegotiationStatus(models.IntegerChoices): ACCEPTED = 3, "Accepted" +class ListingStatus(models.IntegerChoices): + OPEN = 1, "OPEN" + CLOSED = 2, "CLOSED" + + # MANAGERS @@ -218,6 +223,9 @@ class Listing(BaseModel): _("Repayment Amount"), null=True, blank=True ) duration = models.PositiveIntegerField(_("Loan Duration"), null=True, blank=True) + status = models.IntegerField( + choices=ListingStatus.choices, default=ListingStatus.OPEN + ) def __str__(self) -> str: return f"Token: {self.token_contract_address}, NFT: {self.nft_contract_address}" diff --git a/core/serializers.py b/core/serializers.py index 8f66415..bcc89c9 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -1,3 +1,4 @@ +from django.conf import settings from rest_framework import exceptions as rest_exceptions from rest_framework import serializers @@ -117,3 +118,104 @@ def save(self) -> dict: data["is_new"] = is_new token_info = CoreService.generate_auth_token_data(user) return {**data, **token_info} + + +class OfferSerializer(serializers.Serializer): + class Meta: + model = models.Offer + fields = "__all__" + + +class SimpleOfferSerializer(serializers.Serializer): + class Meta: + model = models.Offer + exclude = [ + "signature", + "signature_expiry", + "signature_chain_id", + "signature_unique_id", + ] + + +class MakeOfferSerializer(serializers.Serializer): + listing = serializers.UUIDField() + principal = serializers.IntegerField(min_value=1) + repayment_amount = serializers.IntegerField(min_value=1) + collateral_contract = serializers.CharField() + collateral_id = serializers.IntegerField(min_value=1) + token_contract = serializers.CharField() + loan_duration = serializers.IntegerField( + min_value=settings.MIN_LOAN_DURATION, max_value=settings.MAX_LOAN_DURATION + ) + expiry = serializers.IntegerField() + chain_id = serializers.CharField() + unique_id = serializers.IntegerField() + signatures = serializers.ListField(child=serializers.CharField()) + + def validate(self, attrs): + """ + Validate the request data + """ + # Listing Validation + try: + listing = models.Listing.objects.get(id=attrs["listing"]) + except models.Listing.DoesNotExist: + raise serializers.ValidationError({"detail": "Listing does not exist"}) + if listing.status != models.ListingStatus.OPEN: + raise serializers.ValidationError({"detail": "Listing is not active"}) + # Collateral Contract Validation + if listing.nft_contract_address != attrs["collateral_contract"]: + raise serializers.ValidationError({"detail": "Invalid listing collateral"}) + + # Principal and Repayment Validation + if attrs["principal"] > attrs["repayment_amount"]: + raise serializers.ValidationError( + {"detail": "Principal should be less tha Repayment amount"} + ) + + # validate offer token + if not models.AcceptedToken.objects.filter( + contract_address=attrs["token_contract"] + ).exists(): + raise serializers.ValidationError({"detail": "Token not supported"}) + + # verify the signature + data = { + "principal": attrs["prinicipal"], + "repayment_amount": attrs["repayment_amount"], + "collateral_contract": attrs["collateral_contract"], + "collateral_id": attrs["collateral_id"], + "token_contract": attrs["token_contract"], + "loan_duration": attrs["loan_duration"], + "expiry": attrs["expiry"], + "chain_id": attrs["chain_id"], + "unique_id": attrs["unique_id"], + } + user = self.context["user"] + + check = CoreService.validate_loan_offer_request(data, attrs["signatures"], user) + if not check: + raise serializers.ValidationError({"detail": "Invalid signature message"}) + + # set the necessary contexts + self.context["listing"] = listing + + return attrs + + def save(self, **kwargs): + user = self.context["user"] + listing = self.context["listing"] + offer = CoreService.create_offer( + user, + listing, + self.validated_data["principal"], + self.validated_data["repayment_amount"], + self.validated_data["duration"], + self.validated_data["signature"], + self.validated_data["expiry"], + self.validated_data["chain_id"], + self.validated_data["unique_id"], + ) + data = OfferSerializer(offer).data + + return data diff --git a/core/service.py b/core/service.py index 4b28c6f..6aedf83 100644 --- a/core/service.py +++ b/core/service.py @@ -1,3 +1,5 @@ +import json + from rest_framework_simplejwt.tokens import RefreshToken from starknet_py.utils.typed_data import TypedData @@ -23,6 +25,24 @@ def generate_auth_token_data(user: models.User) -> dict: } return token_data + @classmethod + def validate_loan_offer_request( + cls, data: dict, signatures: list[str], user: models.User + ): + """ + Validate the signed data that verifies the offer create functionality. + The data is signed by the user's wallet and it's components + are sent for verification + Args: + data(dict): the data required to complete the signature request + signatures(list[str]): the signatures of the message + user: The user that is signing the message, + + """ + request_format = SignatureUtils.offer_typed_data_format() + typed_data = SignatureUtils.generate_signature_typed_data(data, request_format) + return SignatureUtils.verify_signatures(typed_data, signatures, user.public_key) + @classmethod def validate_login_request( cls, signatures: list[str], public_key: list[str] @@ -30,7 +50,7 @@ def validate_login_request( """ Validate the Signed data that verifies the login of the user. The data is signed by the user's wallet and it's components - are sent for verifiction + are sent for verification Args: signatures(list[str]): the signatures of the message public_key: The public key of the signer. @@ -55,3 +75,36 @@ def login_or_register_user(cls, public_key: str) -> tuple[models.User, bool]: """ user, created = models.User.objects.get_or_create(public_key=public_key) return user, created + + @classmethod + def create_offer( + cls, + user: models.User, + listing: models.Listing, + principal: int, + repayment_amount: int, + duration: int, + signature: list[str], + signature_expiry: int, + signature_chain_id: int, + signature_unique_id: int, + ) -> models.Offer: + """ + Create an Offer with all the required data. + convert the signature to string json and save it as string in the db + Returns: + models.Offer: the newly created offer instance + """ + offer = models.Offer() + offer.user = user + offer.listing = listing + offer.borrow_amount = principal + offer.repayment_amount = repayment_amount + offer.duration = duration + # convert signature to json string and save as string to db + offer.signature = json.dumps(signature) + offer.signature_expiry = signature_expiry + offer.signature_chain_id = signature_chain_id + offer.signature_unique_id = signature_unique_id + offer.save() + return offer diff --git a/core/urls.py b/core/urls.py index 9e737a1..637414a 100644 --- a/core/urls.py +++ b/core/urls.py @@ -18,4 +18,5 @@ views.SignInAPIView.as_view(), name="signin", ), + path("offer/create/", views.OfferCreateAPIView.as_view(), name="create-offer"), ] diff --git a/core/utils.py b/core/utils.py index 8cb12e6..6c87105 100644 --- a/core/utils.py +++ b/core/utils.py @@ -43,6 +43,46 @@ def login_typed_data_format(cls) -> dict: } return data.copy() + @classmethod + def offer_typed_data_format(cls) -> dict: + """ + This represents the signature request of the offer operation. + + Returns: + dict: The signature request structure of the offer functionality. + """ + data = { + "domain": Domain( + **{ + "name": DOMAIN_NAME, + "chain_id": CHAIN_ID, + "version": VERSION, + } + ), + "types": { + "StarknetDomain": [ + Parameter(**{"name": "name", "type": "felt"}), + Parameter(**{"name": "chainId", "type": "felt"}), + Parameter(**{"name": "version", "type": "felt"}), + ], + "Message": [ + Parameter(**{"name": "principal", "type": "felt"}), + Parameter(**{"name": "repayment_amount", "type": "felt"}), + Parameter(**{"name": "collateral_contract", "type": "felt"}), + Parameter(**{"name": "collateral_id", "type": "felt"}), + Parameter(**{"name": "token_contract", "type": "felt"}), + Parameter(**{"name": "loan_duration", "type": "felt"}), + Parameter(**{"name": "lender", "type": "felt"}), + Parameter(**{"name": "expiry", "type": "felt"}), + Parameter(**{"name": "chain_id", "type": "felt"}), + Parameter(**{"name": "unique_id", "type": "felt"}), + ], + }, + "primary_type": "Message", + "message": {}, + } + return data.copy() + @classmethod def generate_signature_typed_data(cls, data: dict, type_format: dict) -> TypedData: """ diff --git a/core/validators.py b/core/validators.py new file mode 100644 index 0000000..1ef184e --- /dev/null +++ b/core/validators.py @@ -0,0 +1,13 @@ +from rest_framework import serializers + + +def is_positive_number(value: str): + """ + Validate that the value is a positive integer + """ + try: + val = int(value) + if val <= 0: + raise ValueError + except Exception: + raise serializers.ValidationError("This field must be a positive integer") diff --git a/core/views.py b/core/views.py index ef11055..34e07bc 100644 --- a/core/views.py +++ b/core/views.py @@ -1,5 +1,7 @@ # Create your views here. +from rest_framework import status from rest_framework.generics import GenericAPIView, ListAPIView +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from . import models, serializers @@ -24,3 +26,15 @@ def post(self, request): data = serializer.save() return Response(data) + + +class OfferCreateAPIView(GenericAPIView): + serializer_class = serializers.MakeOfferSerializer + permission_classes = [IsAuthenticated] + + def post(self, request): + user = request.user + serializer = self.serializer_class(data=request.data, context={"user": user}) + serializer.is_valid(raise_exception=True) + data = serializer.save() + return Response(data, status=status.HTTP_201_CREATED) diff --git a/poetry.lock b/poetry.lock index 6ddd954..be7b8a4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1777,12 +1777,12 @@ doc = ["sphinx"] [[package]] name = "starknet-py" -version = "0.25.0" +version = "0.26.2" description = "A python SDK for Starknet" optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "starknet_py-0.25.0.tar.gz", hash = "sha256:452f2c9ed5e235540209ec2042d22dcf79c6cec2a6850f5b518d61b3fada3706"}, + {file = "starknet_py-0.26.2.tar.gz", hash = "sha256:afad0eab1d019a57bbcff7b3d0a6f5f5adea49650fa3ad49f2fabf5cfdd81ab1"}, ] [package.dependencies] @@ -1797,6 +1797,7 @@ marshmallow-oneofschema = ">=3.1.1,<4.0.0" poseidon-py = "0.1.5" pycryptodome = ">=3.17,<4.0" typing-extensions = ">=4.3.0,<5.0.0" +websockets = ">=15.0.1,<16.0.0" [package.extras] docs = ["enum-tools[sphinx] (==0.12.0)", "furo (>=2024.5.6,<2025.0.0)", "sphinx (>=4.3.1,<8.0.0)"] @@ -1902,6 +1903,84 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "websockets" +version = "15.0.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, + {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, + {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, + {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, + {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, + {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, + {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, + {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, + {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, + {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, + {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, + {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, + {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, +] + [[package]] name = "yarl" version = "1.18.3" @@ -2001,4 +2080,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = ">=3.12,<3.13" -content-hash = "d1cbeffce2330158b0c37b11d2fb20fc552ffa916e117d08b9947fd2d9731b0e" +content-hash = "004fbac7b3ed626a8c2826ae5684f94dc4b2795c9e82d4178a5cef794f393f26" diff --git a/pyproject.toml b/pyproject.toml index e9d4ebb..647e50e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ django = "4.2" djangorestframework = "^3.15.2" python-dotenv = "^1.0.1" django-rest-knox = "^5.0.2" -starknet-py = "^0.25.0" +starknet-py = "0.26.2" pre-commit = "^4.1.0" dj-database-url = "^2.3.0" django-cors-headers = "^4.7.0" diff --git a/trajectfi/settings.py b/trajectfi/settings.py index a84de14..a5e1e80 100644 --- a/trajectfi/settings.py +++ b/trajectfi/settings.py @@ -173,6 +173,10 @@ SIG_CHAIN_ID = "SN_SEPOLIA" SIG_VERSION = "0.1.0" +# loan settings +MAX_LOAN_DURATION = timedelta(days=365).seconds +MIN_LOAN_DURATION = timedelta(days=1).seconds + # Custom settings AUTH_USER_MODEL = "core.User"