Skip to content

Commit

Permalink
add support for other emv chains and remove circle api support
Browse files Browse the repository at this point in the history
  • Loading branch information
prettyirrelevant committed Oct 11, 2024
1 parent 7db20fb commit a86a8c0
Show file tree
Hide file tree
Showing 17 changed files with 128 additions and 684 deletions.
14 changes: 7 additions & 7 deletions backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ LINT_PATHS = bridgebloc/ manage.py
include .env.dev

lint:
isort $(LINT_PATHS) --diff --check-only
ruff $(LINT_PATHS)
pylint $(LINT_PATHS)
mypy $(LINT_PATHS) --install-types --non-interactive
uv run isort $(LINT_PATHS) --diff --check-only
uv run ruff $(LINT_PATHS)
uv run pylint $(LINT_PATHS)
uv run mypy $(LINT_PATHS) --install-types --non-interactive

format:
isort $(LINT_PATHS)
ruff $(LINT_PATHS) --fix
black $(LINT_PATHS)
uv run isort $(LINT_PATHS)
uv run ruff $(LINT_PATHS) --fix
uv run black $(LINT_PATHS)

test:
@echo "Running tests..."
Expand Down
6 changes: 4 additions & 2 deletions backend/bridgebloc/apps/conversions/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ class ConversionsConfig(AppConfig):
def ready(self) -> None:
EVMAggregator.initialize(
config=EVMAggregatorConfig(
base_endpoints=settings.BASE_RPC_NODES,
ethereum_endpoints=settings.ETHEREUM_RPC_NODES,
optimism_endpoints=settings.OPTIMISM_RPC_NODES,
avalanche_endpoints=settings.AVALANCHE_RPC_NODES,
polygon_pos_endpoints=settings.POLYGON_POS_RPC_NODES,
arbitrum_one_endpoints=settings.ARBITRUM_ONE_RPC_NODES,
polygon_zkevm_endpoints=settings.POLYGON_ZKEVM_RPC_NODES,
base_testnet_endpoints=settings.BASE_TESTNET_RPC_NODES,
optimism_testnet_endpoints=settings.OPTIMISM_TESTNET_RPC_NODES,
ethereum_testnet_endpoints=settings.ETHEREUM_TESTNET_RPC_NODES,
avalanche_testnet_endpoints=settings.AVALANCHE_TESTNET_RPC_NODES,
polygon_pos_testnet_endpoints=settings.POLYGON_POS_TESTNET_RPC_NODES,
arbitrum_one_testnet_endpoints=settings.ARBITRUM_ONE_TESTNET_RPC_NODES,
polygon_zkevm_testnet_endpoints=settings.POLYGON_ZKEVM_TESTNET_RPC_NODES,
),
)
42 changes: 0 additions & 42 deletions backend/bridgebloc/apps/conversions/constants.py

This file was deleted.

6 changes: 0 additions & 6 deletions backend/bridgebloc/apps/conversions/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ class TokenConversionStepStatus(models.TextChoices):
SUCCESSFUL = 'successful'


class CircleAPIConversionStepType(models.TextChoices):
CONFIRM_DEPOSIT = 'confirm deposit'
SEND_TO_RECIPIENT = 'send to recipient'
CREATE_DEPOSIT_ADDRESS = 'create deposit address'


class CCTPConversionStepType(models.TextChoices):
ATTESTATION_SERVICE_CONFIRMATION = 'attestation service confirmation'
SEND_TO_RECIPIENT = 'send to recipient'
16 changes: 3 additions & 13 deletions backend/bridgebloc/apps/conversions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from bridgebloc.common.fields import EVMAddressField, EVMChainIDField
from bridgebloc.common.models import TimestampedModel, UUIDModel

from .enums import CCTPConversionStepType, CircleAPIConversionStepType, TokenConversionStepStatus
from .enums import CCTPConversionStepType, TokenConversionStepStatus
from .types import ConversionMethod


Expand All @@ -22,7 +22,6 @@ class TokenConversion(UUIDModel, TimestampedModel, models.Model):
source_token = models.ForeignKey(
'tokens.Token',
verbose_name='source token',
related_name='source_circle_api_conversions',
on_delete=models.CASCADE,
blank=False,
)
Expand All @@ -35,29 +34,20 @@ class TokenConversion(UUIDModel, TimestampedModel, models.Model):
destination_token = models.ForeignKey(
'tokens.Token',
verbose_name='destination token',
related_name='destination_circle_api_conversions',
on_delete=models.CASCADE,
blank=False,
)
destination_address = EVMAddressField('destination address', blank=False)
amount = models.DecimalField('amount', max_digits=14, decimal_places=2, blank=False)
"""
Represents the value to be transferred or exchanged based on the bridging method:
1. For tokens bridged via Circle API:
This indicates the quantity of USDC to be transferred.
2. For tokens bridged via CCTP:
1. For tokens bridged via CCTP:
This represents the equivalent value of the source_token in USDC.
"""

@property
def actual_amount(self) -> Decimal:
max_fee = Decimal(20)
if self.conversion_type == ConversionMethod.CIRCLE_API:
fee_charged = min(Decimal(0.04) * self.amount, max_fee)
return self.amount - fee_charged

fee_charged = min(Decimal(0.03) * self.amount, max_fee)
return self.amount - fee_charged

Expand All @@ -73,7 +63,7 @@ class TokenConversionStep(UUIDModel, TimestampedModel, models.Model):
step_type = models.CharField(
'step type',
max_length=150,
choices=CircleAPIConversionStepType.choices + CCTPConversionStepType.choices,
choices=CCTPConversionStepType.choices,
blank=False,
)
metadata = models.JSONField('metadata', blank=False)
Expand Down
79 changes: 1 addition & 78 deletions backend/bridgebloc/apps/conversions/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from typing import Any

from eth_utils.address import to_checksum_address
Expand All @@ -16,23 +15,7 @@
from bridgebloc.evm.types import ChainID

from .models import TokenConversion, TokenConversionStep
from .types import ConversionMethod
from .utils import (
get_cross_chain_bridge_deployment_address,
get_token_messenger_deployment_address,
is_valid_route,
)


class CircleTokenConversionDepositTxHashUpdateSerializer(serializers.Serializer):
tx_hash = serializers.CharField(required=True)

def validate_tx_hash(self, value: str) -> str:
is_valid_hash = re.fullmatch('^0x[a-fA-F0-9]{64}', value)
if not bool(is_valid_hash):
raise serializers.ValidationError('Invalid transaction hash provided')

return value
from .utils import get_cross_chain_bridge_deployment_address, get_token_messenger_deployment_address


class TokenConversionStepSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -65,63 +48,6 @@ class Meta:
)


class CircleAPITokenConversionInitialisationSerializer(serializers.Serializer):
source_chain = serializers.CharField(required=True)
source_token = serializers.CharField(required=True)
destination_chain = serializers.CharField(required=True)
destination_token = serializers.CharField(required=True)
destination_address = serializers.CharField(required=True)
amount = serializers.DecimalField(required=True, max_digits=16, decimal_places=2, min_value=1)

def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
try:
source_chain = ChainID.from_name(attrs['source_chain'])
destination_chain = ChainID.from_name(attrs['destination_chain'])
except ValueError as e:
raise serializers.ValidationError(str(e)) from e

# Only allow testnet for now since Circle Live API requires verification.
if source_chain.is_mainnet() or destination_chain.is_mainnet():
raise serializers.ValidationError('Only testnet network is supported via Circle API for now.')

if source_chain == destination_chain:
raise serializers.ValidationError('source_chain cannot be the same as destination_chain')

if source_chain.is_mainnet() != destination_chain.is_mainnet():
raise serializers.ValidationError(
'Both source_chain and destination_chain must be on the same network (testnet or mainnet)',
)

if not is_valid_route(source_chain, destination_chain, ConversionMethod.CIRCLE_API):
raise serializers.ValidationError('Circle API not supported for the source and destination chain')

try:
source_token = Token.objects.get(address=to_checksum_address(attrs['source_token']), chain_id=source_chain)
if source_token.symbol != 'usdc':
raise serializers.ValidationError('Only USDC bridging is allowed via Circle API')
except Token.DoesNotExist as e:
raise serializers.ValidationError('Token does not exist') from e

try:
destination_token = Token.objects.get(
chain_id=destination_chain,
address=to_checksum_address(attrs['destination_token']),
)
if destination_token.symbol != 'usdc':
raise serializers.ValidationError('Only USDC bridging is allowed via Circle API')
except Token.DoesNotExist as e:
raise serializers.ValidationError('Token does not exist') from e

return {
'amount': attrs['amount'],
'source_chain': source_chain,
'source_token': source_token,
'destination_chain': destination_chain,
'destination_token': destination_token,
'destination_address': to_checksum_address(attrs['destination_address']),
}


class CCTPTokenConversionInitialisationSerializer(serializers.Serializer):
tx_hash = serializers.CharField(required=True)
source_chain = serializers.CharField(required=True)
Expand All @@ -142,9 +68,6 @@ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
'Both source_chain and destination_chain must be on the same network (testnet or mainnet)',
)

if not is_valid_route(source_chain, destination_chain, ConversionMethod.CCTP):
raise serializers.ValidationError('CCTP not supported for the source and destination chain')

evm_client = EVMAggregator().get_client(source_chain) # pylint:disable=no-value-for-parameter
tx_receipt = evm_client.get_transaction_receipt(attrs['tx_hash'])
info = self._validate_tx_receipt(
Expand Down
Loading

0 comments on commit a86a8c0

Please sign in to comment.