def create_transfer_account_for_user(user: User, token: Token, balance: float, is_default: bool = True,
                                     is_ghost: bool = False):
    transfer_account = TransferAccount(bound_entity=user)
    transfer_account.token = token
    transfer_account.balance = balance

    if is_default:
        user.default_transfer_account = transfer_account

    if is_ghost:
        transfer_account.is_ghost = True
示例#2
0
def create_transfer_account(init_database, create_master_organisation):
    from server.models.transfer_account import TransferAccount
    transfer_account = TransferAccount()

    init_database.session.add(transfer_account)
    init_database.session.commit()
    return transfer_account
示例#3
0
 def __init__(self, **kwargs):
     super(Token, self).__init__(**kwargs)
     float_transfer_account = TransferAccount(
         private_key=config.ETH_FLOAT_PRIVATE_KEY,
         account_type=TransferAccountType.FLOAT,
         token=self,
         is_approved=True)
     self.float_account = float_transfer_account
示例#4
0
def create_transfer_account_if_required(blockchain_address, token, account_type=TransferAccountType.EXTERNAL):
    transfer_account = TransferAccount.query.execution_options(show_all=True).filter_by(blockchain_address=blockchain_address).first()
    if transfer_account:
        return transfer_account
    else:
        return TransferAccount(
            blockchain_address=blockchain_address,
            token=token,
            account_type=account_type
        )
示例#5
0
 def __init__(self, chain='ETHEREUM', **kwargs):
     self.chain = chain
     super(Token, self).__init__(**kwargs)
     float_transfer_account = TransferAccount(
         private_key=current_app.config['CHAINS'][
             self.chain]['FLOAT_PRIVATE_KEY'],
         account_type=TransferAccountType.FLOAT,
         token=self,
         is_approved=True)
     self.float_account = float_transfer_account
def create_float_wallet(app):
    print_section_title('Creating/Updating Float Wallet')
    float_wallet = TransferAccount.query.execution_options(
        show_all=True).filter(
            TransferAccount.account_type == TransferAccountType.FLOAT).first()

    if float_wallet is None:
        print('Creating Float Wallet')
        float_wallet = TransferAccount(
            private_key=app.config['ETH_FLOAT_PRIVATE_KEY'],
            account_type=TransferAccountType.FLOAT,
            is_approved=True)
        db.session.add(float_wallet)

        db.session.commit()
示例#7
0
def create_float_transfer_account(app):
    print_section_title('Creating/Updating Float Transfer Accounts')
    tokens = db.session.query(Token)
    for t in tokens:
        if t.float_account_id is None:
            print(f'Creating Float Account for {t.name}')
            float_transfer_account = TransferAccount(
                private_key=app.config['ETH_FLOAT_PRIVATE_KEY'],
                account_type=TransferAccountType.FLOAT,
                token=t,
                is_approved=True
            )
            db.session.add(float_transfer_account)
            db.session.flush()
            t.float_account = float_transfer_account
            db.session.commit()
    print_section_conclusion('Done Creating/Updating Float Wallet')
示例#8
0
def create_float_transfer_account(app):
    print_section_title('Creating/Updating Float Transfer Accounts')
    tokens = db.session.query(Token).execution_options(show_all=True)
    for t in tokens:
        if t.float_account is None:
            print(f'Creating Float Account for {t.name}')
            chain_config = app.config['CHAINS'][app.config['DEFAULT_CHAIN']]

            float_transfer_account = TransferAccount(
                private_key=chain_config['FLOAT_PRIVATE_KEY'],
                account_type=TransferAccountType.FLOAT,
                token=t,
                is_approved=True)
            db.session.add(float_transfer_account)
            db.session.flush()
            t.float_account = float_transfer_account
        t.float_account.is_public = True
        db.session.commit()
    print_section_conclusion('Done Creating/Updating Float Wallet')
示例#9
0
class CreditTransfer(ManyOrgBase, BlockchainTaskableBase):
    __tablename__ = 'credit_transfer'

    uuid = db.Column(db.String, unique=True)

    resolved_date = db.Column(db.DateTime)
    _transfer_amount_wei = db.Column(db.Numeric(27), default=0)

    transfer_type = db.Column(db.Enum(TransferTypeEnum), index=True)
    transfer_subtype = db.Column(db.Enum(TransferSubTypeEnum))
    transfer_status = db.Column(db.Enum(TransferStatusEnum),
                                default=TransferStatusEnum.PENDING)
    transfer_mode = db.Column(db.Enum(TransferModeEnum))
    transfer_use = db.Column(JSON)

    transfer_metadata = db.Column(JSONB)

    exclude_from_limit_calcs = db.Column(db.Boolean, default=False)

    resolution_message = db.Column(db.String())

    token_id = db.Column(db.Integer, db.ForeignKey(Token.id))

    sender_transfer_account_id = db.Column(
        db.Integer, db.ForeignKey("transfer_account.id"))
    recipient_transfer_account_id = db.Column(
        db.Integer, db.ForeignKey("transfer_account.id"))

    sender_blockchain_address_id = db.Column(
        db.Integer, db.ForeignKey("blockchain_address.id"))
    recipient_blockchain_address_id = db.Column(
        db.Integer, db.ForeignKey("blockchain_address.id"))

    sender_user_id = db.Column(db.Integer,
                               db.ForeignKey("user.id"),
                               index=True)
    recipient_user_id = db.Column(db.Integer, db.ForeignKey("user.id"))

    attached_images = db.relationship('UploadedResource',
                                      backref='credit_transfer',
                                      lazy=True)

    fiat_ramp = db.relationship('FiatRamp',
                                backref='credit_transfer',
                                lazy=True,
                                uselist=False)

    __table_args__ = (Index('updated_index', "updated"), )

    from_exchange = db.relationship('Exchange',
                                    backref='from_transfer',
                                    lazy=True,
                                    uselist=False,
                                    foreign_keys='Exchange.from_transfer_id')

    to_exchange = db.relationship('Exchange',
                                  backref='to_transfer',
                                  lazy=True,
                                  uselist=False,
                                  foreign_keys='Exchange.to_transfer_id')

    # TODO: Apply this to all transfer amounts/balances, work out the correct denominator size
    @hybrid_property
    def transfer_amount(self):
        return (self._transfer_amount_wei or 0) / int(1e16)

    @transfer_amount.setter
    def transfer_amount(self, val):
        self._transfer_amount_wei = val * int(1e16)

    def send_blockchain_payload_to_worker(self,
                                          is_retry=False,
                                          queue='high-priority'):
        sender_approval = self.sender_transfer_account.get_or_create_system_transfer_approval(
        )

        recipient_approval = self.recipient_transfer_account.get_or_create_system_transfer_approval(
        )
        self.blockchain_task_uuid = bt.make_token_transfer(
            signing_address=self.sender_transfer_account.organisation.
            system_blockchain_address,
            token=self.token,
            from_address=self.sender_transfer_account.blockchain_address,
            to_address=self.recipient_transfer_account.blockchain_address,
            amount=self.transfer_amount,
            prior_tasks=list(
                filter(lambda x: x is not None, [
                    sender_approval.eth_send_task_uuid,
                    sender_approval.approval_task_uuid,
                    recipient_approval.eth_send_task_uuid,
                    recipient_approval.approval_task_uuid
                ])),
            queue=queue)

    def resolve_as_completed(self,
                             existing_blockchain_txn=None,
                             queue='high-priority'):
        self.check_sender_transfer_limits()
        self.resolved_date = datetime.datetime.utcnow()
        self.transfer_status = TransferStatusEnum.COMPLETE
        self.sender_transfer_account.decrement_balance(self.transfer_amount)
        self.recipient_transfer_account.increment_balance(self.transfer_amount)

        if self.transfer_type == TransferTypeEnum.PAYMENT and self.transfer_subtype == TransferSubTypeEnum.DISBURSEMENT:
            if self.recipient_user and self.recipient_user.transfer_card:
                self.recipient_user.transfer_card.update_transfer_card()

        if self.fiat_ramp and self.transfer_type in [
                TransferTypeEnum.DEPOSIT, TransferTypeEnum.WITHDRAWAL
        ]:
            self.fiat_ramp.resolve_as_completed()
        if not existing_blockchain_txn:
            self.send_blockchain_payload_to_worker(queue=queue)

    def resolve_as_rejected(self, message=None):
        if self.fiat_ramp and self.transfer_type in [
                TransferTypeEnum.DEPOSIT, TransferTypeEnum.WITHDRAWAL
        ]:
            self.fiat_ramp.resolve_as_rejected()

        self.resolved_date = datetime.datetime.utcnow()
        self.transfer_status = TransferStatusEnum.REJECTED

        if message:
            self.resolution_message = message

    def get_transfer_limits(self):
        import server.utils.transfer_limits

        return server.utils.transfer_limits.get_transfer_limits(self)

    def check_sender_transfer_limits(self):
        if self.sender_user is None:
            # skip if there is no sender, which implies system send
            return

        relevant_transfer_limits = self.get_transfer_limits()

        for limit in relevant_transfer_limits:

            if limit.no_transfer_allowed:
                raise NoTransferAllowedLimitError(token=self.token.name)

            if limit.transfer_count is not None:
                # GE Limits
                transaction_count = limit.apply_all_filters(
                    self,
                    db.session.query(
                        func.count(CreditTransfer.id).label('count'))
                ).execution_options(show_all=True).first().count

                if (transaction_count or 0) > limit.transfer_count:
                    message = 'Account Limit "{}" reached. Allowed {} transaction per {} days'\
                        .format(limit.name, limit.transfer_count, limit.time_period_days)
                    self.resolve_as_rejected(message=message)
                    raise TransferCountLimitError(
                        transfer_count_limit=limit.transfer_count,
                        limit_time_period_days=limit.time_period_days,
                        token=self.token.name,
                        message=message)

            if limit.transfer_balance_fraction is not None:
                allowed_transfer = limit.transfer_balance_fraction * self.sender_transfer_account.balance

                if self.transfer_amount > allowed_transfer:
                    message = 'Account % Limit "{}" reached. {} available'.format(
                        limit.name, max(allowed_transfer, 0))
                    self.resolve_as_rejected(message=message)
                    raise TransferBalanceFractionLimitError(
                        transfer_balance_fraction_limit=limit.
                        transfer_balance_fraction,
                        transfer_amount_avail=int(allowed_transfer),
                        limit_time_period_days=limit.time_period_days,
                        token=self.token.name,
                        message=message)

            if limit.total_amount is not None:
                # Sempo Compliance Account Limits

                transaction_volume = limit.apply_all_filters(
                    self,
                    db.session.query(
                        func.sum(CreditTransfer.transfer_amount).label('total')
                    )).execution_options(show_all=True).first().total or 0

                if transaction_volume > limit.total_amount:
                    # Don't include the current transaction when reporting amount available
                    amount_avail = limit.total_amount - transaction_volume + int(
                        self.transfer_amount)

                    message = 'Account Limit "{}" reached. {} available'.format(
                        limit.name, max(amount_avail, 0))
                    self.resolve_as_rejected(message=message)
                    raise TransferAmountLimitError(
                        transfer_amount_limit=limit.total_amount,
                        transfer_amount_avail=amount_avail,
                        limit_time_period_days=limit.time_period_days,
                        token=self.token.name,
                        message=message)

        return relevant_transfer_limits

    def check_sender_has_sufficient_balance(self):
        return self.sender_user and self.sender_transfer_account.balance - self.transfer_amount >= 0

    def check_sender_is_approved(self):
        return self.sender_user and self.sender_transfer_account.is_approved

    def check_recipient_is_approved(self):
        return self.recipient_user and self.recipient_transfer_account.is_approved

    def _select_transfer_account(self, token, user):
        if token is None:
            raise Exception("Token must be specified")
        return find_transfer_accounts_with_matching_token(user, token)

    def append_organisation_if_required(self, organisation):
        if organisation and organisation not in self.organisations:
            self.organisations.append(organisation)

    def __init__(self,
                 amount,
                 token=None,
                 sender_user=None,
                 recipient_user=None,
                 sender_transfer_account=None,
                 recipient_transfer_account=None,
                 transfer_type: TransferTypeEnum = None,
                 uuid=None,
                 transfer_metadata=None,
                 fiat_ramp=None,
                 transfer_subtype: TransferSubTypeEnum = None,
                 is_ghost_transfer=False):

        if amount < 0:
            raise Exception("Negative amount provided")
        self.transfer_amount = amount

        self.sender_user = sender_user
        self.recipient_user = recipient_user

        self.sender_transfer_account = sender_transfer_account or self._select_transfer_account(
            token, sender_user)

        self.token = token or self.sender_transfer_account.token

        self.fiat_ramp = fiat_ramp

        try:
            self.recipient_transfer_account = recipient_transfer_account or self._select_transfer_account(
                self.token, recipient_user)

            if is_ghost_transfer is False:
                self.recipient_transfer_account.is_ghost = False
        except NoTransferAccountError:
            self.recipient_transfer_account = TransferAccount(
                bound_entity=recipient_user,
                token=token,
                is_approved=True,
                is_ghost=is_ghost_transfer)
            db.session.add(self.recipient_transfer_account)

        if transfer_type is TransferTypeEnum.DEPOSIT:
            self.sender_transfer_account = self.recipient_transfer_account.get_float_transfer_account(
            )

        if transfer_type is TransferTypeEnum.WITHDRAWAL:
            self.recipient_transfer_account = self.sender_transfer_account.get_float_transfer_account(
            )

        if self.sender_transfer_account.token != self.recipient_transfer_account.token:
            raise Exception("Tokens do not match")

        self.transfer_type = transfer_type
        self.transfer_subtype = transfer_subtype
        self.transfer_metadata = transfer_metadata

        if uuid is not None:
            self.uuid = uuid

        self.append_organisation_if_required(
            self.recipient_transfer_account.organisation)
        self.append_organisation_if_required(
            self.sender_transfer_account.organisation)
示例#10
0
    def __init__(self,
                 amount,
                 token=None,
                 sender_user=None,
                 recipient_user=None,
                 sender_transfer_account=None,
                 recipient_transfer_account=None,
                 transfer_type: TransferTypeEnum = None,
                 uuid=None,
                 transfer_metadata=None,
                 fiat_ramp=None,
                 transfer_subtype: TransferSubTypeEnum = None,
                 is_ghost_transfer=False):

        if amount < 0:
            raise Exception("Negative amount provided")
        self.transfer_amount = amount

        self.sender_user = sender_user
        self.recipient_user = recipient_user

        self.sender_transfer_account = sender_transfer_account or self._select_transfer_account(
            token, sender_user)

        self.token = token or self.sender_transfer_account.token

        self.fiat_ramp = fiat_ramp

        try:
            self.recipient_transfer_account = recipient_transfer_account or self._select_transfer_account(
                self.token, recipient_user)

            if is_ghost_transfer is False:
                self.recipient_transfer_account.is_ghost = False
        except NoTransferAccountError:
            self.recipient_transfer_account = TransferAccount(
                bound_entity=recipient_user,
                token=token,
                is_approved=True,
                is_ghost=is_ghost_transfer)
            db.session.add(self.recipient_transfer_account)

        if transfer_type is TransferTypeEnum.DEPOSIT:
            self.sender_transfer_account = self.recipient_transfer_account.get_float_transfer_account(
            )

        if transfer_type is TransferTypeEnum.WITHDRAWAL:
            self.recipient_transfer_account = self.sender_transfer_account.get_float_transfer_account(
            )

        if self.sender_transfer_account.token != self.recipient_transfer_account.token:
            raise Exception("Tokens do not match")

        self.transfer_type = transfer_type
        self.transfer_subtype = transfer_subtype
        self.transfer_metadata = transfer_metadata

        if uuid is not None:
            self.uuid = uuid

        self.append_organisation_if_required(
            self.recipient_transfer_account.organisation)
        self.append_organisation_if_required(
            self.sender_transfer_account.organisation)
示例#11
0
def create_transfer_account_user(first_name=None,
                                 last_name=None,
                                 preferred_language=None,
                                 phone=None,
                                 email=None,
                                 public_serial_number=None,
                                 uuid=None,
                                 organisation: Organisation = None,
                                 token=None,
                                 blockchain_address=None,
                                 transfer_account_name=None,
                                 use_precreated_pin=False,
                                 use_last_4_digits_of_id_as_initial_pin=False,
                                 existing_transfer_account=None,
                                 roles=None,
                                 is_self_sign_up=False,
                                 business_usage=None,
                                 initial_disbursement=None):

    user = User(first_name=first_name,
                last_name=last_name,
                preferred_language=preferred_language,
                blockchain_address=blockchain_address,
                phone=phone,
                email=email,
                uuid=uuid,
                public_serial_number=public_serial_number,
                is_self_sign_up=is_self_sign_up,
                business_usage=business_usage)

    precreated_pin = None
    is_activated = False

    try:
        transfer_card = TransferCard.get_transfer_card(public_serial_number)
    except Exception as e:
        transfer_card = None

    if use_precreated_pin:
        precreated_pin = transfer_card.PIN
        is_activated = True

    elif use_last_4_digits_of_id_as_initial_pin:
        precreated_pin = str(public_serial_number or phone)[-4:]
        is_activated = False

    user.set_pin(precreated_pin, is_activated)

    if roles:
        for role in roles:
            user.set_held_role(role[0], role[1])
    else:
        user.remove_all_held_roles()

    if not organisation:
        organisation = Organisation.master_organisation()

    user.add_user_to_organisation(organisation, is_admin=False)

    db.session.add(user)

    if existing_transfer_account:
        transfer_account = existing_transfer_account
        user.transfer_accounts.append(existing_transfer_account)
    else:
        transfer_account = TransferAccount(
            bound_entity=user,
            blockchain_address=blockchain_address,
            organisation=organisation)

        top_level_roles = [r[0] for r in roles or []]
        is_vendor = 'VENDOR' in top_level_roles
        is_beneficiary = 'BENEFICIARY' in top_level_roles

        transfer_account.name = transfer_account_name
        transfer_account.is_vendor = is_vendor
        transfer_account.is_beneficiary = is_beneficiary

        if transfer_card:
            transfer_account.transfer_card = transfer_card

        if token:
            transfer_account.token = token

        if not is_self_sign_up:
            transfer_account.approve_and_disburse(
                initial_disbursement=initial_disbursement)

        db.session.add(transfer_account)

    user.default_transfer_account = transfer_account

    return user
    def __init__(self,
                 amount,
                 token=None,
                 sender_user=None,
                 recipient_user=None,
                 sender_transfer_account=None,
                 recipient_transfer_account=None,
                 transfer_type: TransferTypeEnum=None,
                 uuid=None,
                 transfer_metadata=None,
                 fiat_ramp=None,
                 transfer_subtype: TransferSubTypeEnum=None,
                 transfer_mode: TransferModeEnum = None,
                 is_ghost_transfer=False,
                 require_sufficient_balance=True):

        if amount < 0:
            raise Exception("Negative amount provided")
        self.transfer_amount = amount

        self.sender_user = sender_user
        self.recipient_user = recipient_user

        self.sender_transfer_account = sender_transfer_account or self._select_transfer_account(
            token,
            sender_user
        )

        self.token = token or self.sender_transfer_account.token

        self.fiat_ramp = fiat_ramp

        if transfer_type is TransferTypeEnum.DEPOSIT:
            self.sender_transfer_account = self.recipient_transfer_account.token.float_account

        if transfer_type is TransferTypeEnum.WITHDRAWAL:
            self.recipient_transfer_account = self.sender_transfer_account.token.float_account

        try:
            self.recipient_transfer_account = recipient_transfer_account or self.recipient_transfer_account or self._select_transfer_account(
                self.token,
                recipient_user
            )

            if is_ghost_transfer is False:
                self.recipient_transfer_account.is_ghost = False
        except NoTransferAccountError:
            self.recipient_transfer_account = TransferAccount(
                bound_entity=recipient_user,
                token=token,
                is_approved=True,
                is_ghost=is_ghost_transfer
            )
            db.session.add(self.recipient_transfer_account)

        if self.sender_transfer_account.token != self.recipient_transfer_account.token:
            raise Exception("Tokens do not match")

        self.transfer_type = transfer_type
        self.transfer_subtype = transfer_subtype
        self.transfer_mode = transfer_mode
        self.transfer_metadata = transfer_metadata

        if uuid is not None:
            self.uuid = uuid

        self.append_organisation_if_required(self.recipient_transfer_account.organisation)
        self.append_organisation_if_required(self.sender_transfer_account.organisation)

        if require_sufficient_balance and not self.check_sender_has_sufficient_balance():
            message = "Sender {} has insufficient balance. Has {}, needs {}.".format(
                self.sender_transfer_account,
                self.sender_transfer_account.balance,
                self.transfer_amount
            )
            self.resolve_as_rejected(message)
            raise InsufficientBalanceError(message)

        self.update_balances()
示例#13
0
def new_transfer_account(create_master_organisation):
    from server.models.transfer_account import TransferAccount
    return TransferAccount()
示例#14
0
def create_transfer_account_user(first_name=None,
                                 last_name=None,
                                 preferred_language=None,
                                 phone=None,
                                 email=None,
                                 public_serial_number=None,
                                 organisation: Organisation = None,
                                 token=None,
                                 blockchain_address=None,
                                 transfer_account_name=None,
                                 lat=None,
                                 lng=None,
                                 use_precreated_pin=False,
                                 use_last_4_digits_of_id_as_initial_pin=False,
                                 existing_transfer_account=None,
                                 is_beneficiary=False,
                                 is_vendor=False,
                                 is_tokenagent=False,
                                 is_groupaccount=False,
                                 is_self_sign_up=False,
                                 business_usage=None,
                                 initial_disbursement=None):

    user = User(first_name=first_name,
                last_name=last_name,
                lat=lat,
                lng=lng,
                preferred_language=preferred_language,
                phone=phone,
                email=email,
                public_serial_number=public_serial_number,
                is_self_sign_up=is_self_sign_up,
                business_usage=business_usage)

    precreated_pin = None
    is_activated = False

    try:
        transfer_card = TransferCard.get_transfer_card(public_serial_number)
    except Exception as e:
        transfer_card = None

    if use_precreated_pin:
        precreated_pin = transfer_card.PIN
        is_activated = True

    elif use_last_4_digits_of_id_as_initial_pin:
        precreated_pin = str(public_serial_number or phone)[-4:]
        is_activated = False

    user.set_pin(precreated_pin, is_activated)

    if not is_vendor:
        vendor_tier = None
    elif existing_transfer_account:
        vendor_tier = 'vendor'
    else:
        vendor_tier = 'supervendor'

    user.set_held_role('VENDOR', vendor_tier)

    if is_tokenagent:
        user.set_held_role('TOKEN_AGENT', 'grassroots_token_agent')

    if is_groupaccount:
        user.set_held_role('GROUP_ACCOUNT', 'grassroots_group_account')

    if is_beneficiary:
        user.set_held_role('BENEFICIARY', 'beneficiary')

    if not organisation:
        organisation = Organisation.master_organisation()

    user.add_user_to_organisation(organisation, is_admin=False)

    db.session.add(user)

    if existing_transfer_account:
        transfer_account = existing_transfer_account
        user.transfer_accounts.append(existing_transfer_account)
    else:
        transfer_account = TransferAccount(
            bound_entity=user,
            blockchain_address=blockchain_address,
            organisation=organisation)

        transfer_account.name = transfer_account_name
        transfer_account.is_vendor = is_vendor
        transfer_account.is_beneficiary = is_beneficiary

        if transfer_card:
            transfer_account.transfer_card = transfer_card

        if token:
            transfer_account.token = token

        if not is_self_sign_up:
            transfer_account.approve_and_disburse(
                initial_disbursement=initial_disbursement)

        db.session.add(transfer_account)

    user.default_transfer_account = transfer_account

    return user
示例#15
0
class CreditTransfer(ManyOrgBase, BlockchainTaskableBase):
    __tablename__ = 'credit_transfer'

    uuid = db.Column(db.String, unique=True)

    resolved_date = db.Column(db.DateTime)
    _transfer_amount_wei = db.Column(db.Numeric(27), default=0)

    transfer_type = db.Column(db.Enum(TransferTypeEnum), index=True)
    transfer_subtype = db.Column(db.Enum(TransferSubTypeEnum))
    transfer_status = db.Column(db.Enum(TransferStatusEnum),
                                default=TransferStatusEnum.PENDING)
    transfer_mode = db.Column(db.Enum(TransferModeEnum))
    transfer_use = db.Column(JSON)
    transfer_metadata = db.Column(JSONB)

    exclude_from_limit_calcs = db.Column(db.Boolean, default=False)

    resolution_message = db.Column(db.String())

    token_id = db.Column(db.Integer, db.ForeignKey(Token.id))

    sender_transfer_account_id = db.Column(
        db.Integer, db.ForeignKey("transfer_account.id"))
    recipient_transfer_account_id = db.Column(
        db.Integer, db.ForeignKey("transfer_account.id"))

    sender_blockchain_address_id = db.Column(
        db.Integer, db.ForeignKey("blockchain_address.id"))
    recipient_blockchain_address_id = db.Column(
        db.Integer, db.ForeignKey("blockchain_address.id"))

    sender_user_id = db.Column(db.Integer,
                               db.ForeignKey("user.id"),
                               index=True)
    recipient_user_id = db.Column(db.Integer, db.ForeignKey("user.id"))

    attached_images = db.relationship('UploadedResource',
                                      backref='credit_transfer',
                                      lazy=True)

    fiat_ramp = db.relationship('FiatRamp',
                                backref='credit_transfer',
                                lazy=True,
                                uselist=False)

    __table_args__ = (Index('updated_index', "updated"), )

    from_exchange = db.relationship('Exchange',
                                    backref='from_transfer',
                                    lazy=True,
                                    uselist=False,
                                    foreign_keys='Exchange.from_transfer_id')

    to_exchange = db.relationship('Exchange',
                                  backref='to_transfer',
                                  lazy=True,
                                  uselist=False,
                                  foreign_keys='Exchange.to_transfer_id')

    # TODO: Apply this to all transfer amounts/balances, work out the correct denominator size
    @hybrid_property
    def transfer_amount(self):
        return (self._transfer_amount_wei or 0) / int(1e16)

    @transfer_amount.setter
    def transfer_amount(self, val):
        self._transfer_amount_wei = val * int(1e16)

    def send_blockchain_payload_to_worker(self,
                                          is_retry=False,
                                          queue='high-priority'):
        sender_approval = self.sender_transfer_account.get_or_create_system_transfer_approval(
        )
        recipient_approval = self.recipient_transfer_account.get_or_create_system_transfer_approval(
        )
        return bt.make_token_transfer(
            signing_address=self.sender_transfer_account.organisation.
            system_blockchain_address,
            token=self.token,
            from_address=self.sender_transfer_account.blockchain_address,
            to_address=self.recipient_transfer_account.blockchain_address,
            amount=self.transfer_amount,
            prior_tasks=list(
                filter(lambda x: x is not None, [
                    sender_approval.eth_send_task_uuid,
                    sender_approval.approval_task_uuid,
                    recipient_approval.eth_send_task_uuid,
                    recipient_approval.approval_task_uuid
                ])),
            queue=queue,
            task_uuid=self.blockchain_task_uuid)

    def resolve_as_completed(self,
                             existing_blockchain_txn=None,
                             queue='high-priority'):
        if self.transfer_status not in [None, TransferStatusEnum.PENDING]:
            raise Exception(
                f'Transfer resolve function called multiple times for transaciton {self.id}'
            )
        self.check_sender_transfer_limits()
        self.resolved_date = datetime.datetime.utcnow()
        self.transfer_status = TransferStatusEnum.COMPLETE
        self.sender_transfer_account.decrement_balance(self.transfer_amount)
        self.recipient_transfer_account.increment_balance(self.transfer_amount)

        if self.transfer_type == TransferTypeEnum.PAYMENT and self.transfer_subtype == TransferSubTypeEnum.DISBURSEMENT:
            if self.recipient_user and self.recipient_user.transfer_card:
                self.recipient_user.transfer_card.update_transfer_card()

        if self.fiat_ramp and self.transfer_type in [
                TransferTypeEnum.DEPOSIT, TransferTypeEnum.WITHDRAWAL
        ]:
            self.fiat_ramp.resolve_as_completed()
        if not existing_blockchain_txn:
            self.blockchain_task_uuid = str(uuid4())
            g.pending_transactions.append((self, queue))

    def resolve_as_rejected(self, message=None):
        if self.transfer_status not in [None, TransferStatusEnum.PENDING]:
            raise Exception(
                f'Transfer resolve function called multiple times for transaciton {self.id}'
            )

        if self.fiat_ramp and self.transfer_type in [
                TransferTypeEnum.DEPOSIT, TransferTypeEnum.WITHDRAWAL
        ]:
            self.fiat_ramp.resolve_as_rejected()

        self.resolved_date = datetime.datetime.utcnow()
        self.transfer_status = TransferStatusEnum.REJECTED

        if message:
            self.resolution_message = message

    def get_transfer_limits(self):
        from server.utils.transfer_limits import (
            LIMIT_IMPLEMENTATIONS, get_applicable_transfer_limits)

        return get_applicable_transfer_limits(LIMIT_IMPLEMENTATIONS, self)

    def check_sender_transfer_limits(self):
        if self.sender_user is None:
            # skip if there is no sender, which implies system send
            return

        relevant_transfer_limits = self.get_transfer_limits()

        for limit in relevant_transfer_limits:

            try:
                limit.validate_transfer(self)
            except (TransferAmountLimitError, TransferCountLimitError,
                    TransferBalanceFractionLimitError,
                    MaximumPerTransferLimitError, MinimumSentLimitError,
                    NoTransferAllowedLimitError) as e:
                self.resolve_as_rejected(message=e.message)
                raise e

        return relevant_transfer_limits

    def check_sender_has_sufficient_balance(self):
        return self.sender_user and self.sender_transfer_account.balance - self.transfer_amount >= 0

    def check_sender_is_approved(self):
        return self.sender_user and self.sender_transfer_account.is_approved

    def check_recipient_is_approved(self):
        return self.recipient_user and self.recipient_transfer_account.is_approved

    def _select_transfer_account(self, token, user):
        if token is None:
            raise Exception("Token must be specified")
        return find_transfer_accounts_with_matching_token(user, token)

    def append_organisation_if_required(self, organisation):
        if organisation and organisation not in self.organisations:
            self.organisations.append(organisation)

    def __init__(self,
                 amount,
                 token=None,
                 sender_user=None,
                 recipient_user=None,
                 sender_transfer_account=None,
                 recipient_transfer_account=None,
                 transfer_type: TransferTypeEnum = None,
                 uuid=None,
                 transfer_metadata=None,
                 fiat_ramp=None,
                 transfer_subtype: TransferSubTypeEnum = None,
                 transfer_mode: TransferModeEnum = None,
                 is_ghost_transfer=False):

        if amount < 0:
            raise Exception("Negative amount provided")
        self.transfer_amount = amount

        self.sender_user = sender_user
        self.recipient_user = recipient_user

        self.sender_transfer_account = sender_transfer_account or self._select_transfer_account(
            token, sender_user)

        self.token = token or self.sender_transfer_account.token

        self.fiat_ramp = fiat_ramp

        try:
            self.recipient_transfer_account = recipient_transfer_account or self._select_transfer_account(
                self.token, recipient_user)

            if is_ghost_transfer is False:
                self.recipient_transfer_account.is_ghost = False
        except NoTransferAccountError:
            self.recipient_transfer_account = TransferAccount(
                bound_entity=recipient_user,
                token=token,
                is_approved=True,
                is_ghost=is_ghost_transfer)
            db.session.add(self.recipient_transfer_account)

        if transfer_type is TransferTypeEnum.DEPOSIT:
            self.sender_transfer_account = self.recipient_transfer_account.get_float_transfer_account(
            )

        if transfer_type is TransferTypeEnum.WITHDRAWAL:
            self.recipient_transfer_account = self.sender_transfer_account.get_float_transfer_account(
            )

        if self.sender_transfer_account.token != self.recipient_transfer_account.token:
            raise Exception("Tokens do not match")

        self.transfer_type = transfer_type
        self.transfer_subtype = transfer_subtype
        self.transfer_mode = transfer_mode
        self.transfer_metadata = transfer_metadata

        if uuid is not None:
            self.uuid = uuid

        self.append_organisation_if_required(
            self.recipient_transfer_account.organisation)
        self.append_organisation_if_required(
            self.sender_transfer_account.organisation)