def make_deposit_transfer(transfer_amount, token, receive_account, transfer_mode=None, automatically_resolve_complete=True, uuid=None, fiat_ramp=None): """ This is used for a user depositing funds to their Sempo wallet. Only interacts with Sempo float. :param transfer_amount: :param token: :param receive_account: :param transfer_mode: :param automatically_resolve_complete: :param uuid: :param fiat_ramp: A FiatRamp Object to tie to credit transfer :return: """ transfer = CreditTransfer(amount=transfer_amount, token=token, recipient_user=receive_account, transfer_type=TransferTypeEnum.DEPOSIT, transfer_mode=transfer_mode, uuid=uuid, fiat_ramp=fiat_ramp) if automatically_resolve_complete: transfer.resolve_as_completed() return transfer
def migrate_balances(self, ge_address_to_user: dict): org = Organisation.query.get(self.sempo_organisation_id) token = org.token ge_address_to_user.pop(None, None) addresses = list(ge_address_to_user.keys()) for user_address in addresses: try: balance_wei = 0 for ge_token in GE_MIGRATION_TOKENS.keys(): contract_address = GE_MIGRATION_TOKENS[ge_token] v = get_token_balance(user_address, contract_address) if v != '': balance_wei += int(v) user = ge_address_to_user[user_address] print(f'transfering {balance_wei} wei to {user}') if balance_wei != 0: migration_transfer = CreditTransfer( amount=balance_wei / 1e16, token=token, sender_transfer_account=org. queried_org_level_transfer_account, recipient_user=user, transfer_type=TransferTypeEnum.PAYMENT, transfer_subtype=TransferSubTypeEnum.DISBURSEMENT) db.session.add(migration_transfer) migration_transfer.resolve_as_completed() except Exception as e: print(e) sentry_sdk.capture_exception(e) pass
def post(self): # Handle Parameters post_data = request.get_json() recipient_blockchain_address = post_data.get( 'recipient_blockchain_address', None) transfer_amount = post_data.get('transfer_amount', None) master_wallet = g.active_organisation.org_level_transfer_account receive_transfer_account = create_transfer_account_if_required( recipient_blockchain_address, g.active_organisation.token, TransferAccountType.EXTERNAL) error_message = None if not recipient_blockchain_address: error_message = '"recipient_blockchain_address" parameter required' if not transfer_amount: error_message = '"transfer_amount" parameter required' if not error_message and not isinstance(transfer_amount, int): try: transfer_amount = int(transfer_amount) except: error_message = '"transfer_amount must be an integer' if not master_wallet: error_message = f'Organisation {g.active_organisation.id} has no master wallet' if error_message: return {'message': error_message}, 400 transfer = CreditTransfer( transfer_amount, g.active_organisation.token, sender_transfer_account=master_wallet, recipient_transfer_account=receive_transfer_account, transfer_type=TransferTypeEnum. PAYMENT, # PAYMENT, since WITHDRAWAL is to the float account by definition require_sufficient_balance=True) # Make the transfer! transfer.resolve_as_complete_and_trigger_blockchain() credit_transfer = credit_transfer_schema.dump(transfer) response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } return response_object, 201
def make_withdrawal_transfer(transfer_amount, token, send_account, transfer_mode=None, require_sender_approved=True, require_sufficient_balance=True, automatically_resolve_complete=True, uuid=None): """ This is used for a user withdrawing funds from their Sempo wallet. Only interacts with Sempo float. :param transfer_amount: :param token: :param send_account: :param transfer_mode: :param require_sender_approved: :param require_sufficient_balance: :param automatically_resolve_complete: :param uuid: :return: """ transfer = CreditTransfer(transfer_amount, token, sender_user=send_account, uuid=uuid, transfer_type=TransferTypeEnum.WITHDRAWAL, transfer_mode=transfer_mode) if require_sender_approved and not transfer.check_sender_is_approved(): message = "Sender {} is not approved".format(send_account) transfer.resolve_as_rejected(message) raise AccountNotApprovedError(message, is_sender=True) if require_sufficient_balance and not transfer.check_sender_has_sufficient_balance( ): message = "Sender {} has insufficient balance".format(send_account) transfer.resolve_as_rejected(message) raise InsufficientBalanceError(message) if automatically_resolve_complete: transfer.resolve_as_completed() pusher.push_admin_credit_transfer(transfer) return transfer
def create_transfer(amount, sender_user, recipient_user, token, subtype=None): transfer = CreditTransfer( amount=amount, sender_user=sender_user, recipient_user=recipient_user, token=token, uuid=str(uuid4())) db.session.add(transfer) # Mimics before_request hook g.pending_transactions = [] transfer.resolve_as_completed() # Mimics after_request hook for transaction, queue in g.pending_transactions: transaction.send_blockchain_payload_to_worker(queue=queue) transfer.transfer_type = TransferTypeEnum.PAYMENT transfer.transfer_subtype = subtype # Commit to prevent memory errors with large numbers of txns counts db.session.commit() return transfer
def new_credit_transfer(create_transfer_account_user, external_reserve_token): from server.models.credit_transfer import CreditTransfer from uuid import uuid4 credit_transfer = CreditTransfer( amount=1000, token=external_reserve_token, sender_user=create_transfer_account_user, recipient_user=create_transfer_account_user, transfer_type=TransferTypeEnum.PAYMENT, transfer_subtype=TransferSubTypeEnum.STANDARD, uuid=str(uuid4())) return credit_transfer
def other_new_credit_transfer(create_transfer_account_user, external_reserve_token): # Janky copy paste job because of how pytest works from server.models.credit_transfer import CreditTransfer from uuid import uuid4 credit_transfer = CreditTransfer( amount=1000, token=external_reserve_token, sender_user=create_transfer_account_user, recipient_user=create_transfer_account_user, transfer_type=TransferTypeEnum.PAYMENT, transfer_subtype=TransferSubTypeEnum.STANDARD, uuid=str(uuid4()) ) return credit_transfer
def create_transfer(amount, sender_user, recipient_user, token, subtype=TransferSubTypeEnum.STANDARD, transfer_usages=None, created_offset=0, transfer_mode=None): transfer = CreditTransfer(amount=amount, sender_user=sender_user, recipient_user=recipient_user, token=token, uuid=str(uuid4())) db.session.add(transfer) # Mimics before_request hook g.pending_transactions = [] if transfer_usages: transfer.transfer_usages = transfer_usages transfer.resolve_as_complete_and_trigger_blockchain() transfer.transfer_type = TransferTypeEnum.PAYMENT transfer.transfer_subtype = subtype transfer.transfer_mode = transfer_mode transfer.created = datetime.utcnow() - timedelta(days=created_offset) # Commit to prevent memory errors with large numbers of txns counts db.session.commit() # Mimic after request hook midway through process for transaction, queue in g.pending_transactions: transaction.send_blockchain_payload_to_worker(queue=queue) return transfer
def create_transfer(amount, sender_user, recipient_user, token, subtype=None): transfer = CreditTransfer( amount=amount, sender_user=sender_user, recipient_user=recipient_user, token=token, uuid=str(uuid4())) db.session.add(transfer) transfer.resolve_as_completed() transfer.transfer_type = TransferTypeEnum.PAYMENT transfer.transfer_subtype = subtype return transfer
def create_transfer(amount, sender_user, recipient_user, token, subtype=None): transfer = CreditTransfer( amount=amount, sender_user=sender_user, recipient_user=recipient_user, token=token, uuid=str(uuid4())) db.session.add(transfer) transfer.resolve_as_completed() transfer.transfer_type = TransferTypeEnum.PAYMENT transfer.transfer_subtype = subtype # Commit to prevent memory errors with large numbers of txns counts db.session.commit() return transfer
def make_payment_transfer( transfer_amount, token=None, send_user=None, send_transfer_account=None, receive_user=None, receive_transfer_account=None, transfer_use=None, transfer_mode=None, require_sender_approved=True, require_recipient_approved=True, require_sufficient_balance=True, automatically_resolve_complete=True, uuid=None, transfer_subtype: TransferSubTypeEnum = TransferSubTypeEnum.STANDARD, is_ghost_transfer=False, queue='high-priority'): """ This is used for internal transfers between Sempo wallets. :param transfer_amount: :param token: :param send_user: :param send_transfer_account: :param receive_user: :param receive_transfer_account: :param transfer_use: :param transfer_mode: TransferModeEnum :param require_sender_approved: :param require_recipient_approved: :param require_sufficient_balance: :param automatically_resolve_complete: :param uuid: :param transfer_subtype: accepts TransferSubType str. :param is_ghost_transfer: if an account is created for recipient just to exchange, it's not real :param enable_pusher: :param queue: :return: """ if transfer_subtype is TransferSubTypeEnum.DISBURSEMENT: require_sender_approved = False require_recipient_approved = False require_sufficient_balance = False # primary NGO wallet to disburse from send_transfer_account = receive_user.default_organisation.queried_org_level_transfer_account if transfer_subtype is TransferSubTypeEnum.RECLAMATION: require_sender_approved = False # primary NGO wallet to reclaim to receive_transfer_account = send_user.default_organisation.queried_org_level_transfer_account if transfer_subtype is TransferSubTypeEnum.INCENTIVE: send_transfer_account = receive_transfer_account.get_float_transfer_account( ) transfer = CreditTransfer( transfer_amount, token=token, sender_user=send_user, sender_transfer_account=send_transfer_account, recipient_user=receive_user, recipient_transfer_account=receive_transfer_account, uuid=uuid, transfer_type=TransferTypeEnum.PAYMENT, transfer_subtype=transfer_subtype, transfer_mode=transfer_mode, is_ghost_transfer=is_ghost_transfer) make_cashout_incentive_transaction = False if transfer_use is not None: usages = [] try: use_ids = transfer_use.split(',') # passed as '3,4' etc. except AttributeError: use_ids = transfer_use for use_id in use_ids: if use_id != 'null': use = TransferUsage.query.get(int(use_id)) if use: usages.append(use.name) if use.is_cashout: make_cashout_incentive_transaction = True else: usages.append('Other') transfer.transfer_use = usages transfer.uuid = uuid if require_sender_approved and not transfer.check_sender_is_approved(): message = "Sender {} is not approved".format(send_transfer_account) transfer.resolve_as_rejected(message) raise AccountNotApprovedError(message, is_sender=True) if require_recipient_approved and not transfer.check_recipient_is_approved( ): message = "Recipient {} is not approved".format(receive_user) transfer.resolve_as_rejected(message) raise AccountNotApprovedError(message, is_sender=False) if require_sufficient_balance and not transfer.check_sender_has_sufficient_balance( ): message = "Sender {} has insufficient balance".format( send_transfer_account) transfer.resolve_as_rejected(message) raise InsufficientBalanceError(message) if automatically_resolve_complete: transfer.resolve_as_completed(queue=queue) if make_cashout_incentive_transaction: try: # todo: check limits apply incentive_amount = round( transfer_amount * current_app.config['CASHOUT_INCENTIVE_PERCENT'] / 100) make_payment_transfer( incentive_amount, receive_user=receive_user, transfer_subtype=TransferSubTypeEnum.INCENTIVE, transfer_mode=TransferModeEnum.INTERNAL) except Exception as e: print(e) sentry_sdk.capture_exception(e) return transfer
def post(self): post_data = request.get_json() transfer_amount = post_data.get('transfer_amount') sender_blockchain_address = post_data.get('sender_blockchain_address') recipient_blockchain_address = post_data.get( 'recipient_blockchain_address') blockchain_transaction_hash = post_data.get( 'blockchain_transaction_hash') contract_address = post_data.get('contract_address') transfer = CreditTransfer.query.execution_options( show_all=True).filter_by( blockchain_hash=blockchain_transaction_hash).first() # Case 1: Transfer exists in the database already. Mark received_third_party_sync as true, fetch it, and return it in that case if transfer: transfer.received_third_party_sync = True credit_transfer = credit_transfer_schema.dump(transfer).data response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } else: token = Token.query.filter_by(address=contract_address).first() maybe_sender_transfer_account = TransferAccount.query.execution_options( show_all=True).filter_by( blockchain_address=sender_blockchain_address).first() maybe_sender_user = maybe_sender_transfer_account.users[ 0] if maybe_sender_transfer_account and len( maybe_sender_transfer_account.users) == 1 else None maybe_recipient_transfer_account = TransferAccount.query.execution_options( show_all=True).filter_by( blockchain_address=recipient_blockchain_address).first() maybe_recipient_user = maybe_recipient_transfer_account.users[ 0] if maybe_recipient_transfer_account and len( maybe_recipient_transfer_account.users) == 1 else None # Case 2: Two non-sempo users making a trade on our token. We don't have to track this! if not maybe_recipient_transfer_account and not maybe_sender_transfer_account: response_object = { 'message': 'No existing users involved in this transfer', 'data': {} } # Case 3: Two non-Sempo users, at least one of whom has interacted with Sempo users before transacting with one another # We don't have to track this either! elif ( # The recipient is either an external transfer account we've seen before # OR we haven't seen them before and so can infer they're external ((maybe_recipient_transfer_account and maybe_recipient_transfer_account.account_type == TransferAccountType.EXTERNAL) or not maybe_recipient_transfer_account) and # # And the sender is either an external transfer account we've seen before # OR we haven't seen them before and so can infer they're external ((maybe_sender_transfer_account and maybe_sender_transfer_account.account_type == TransferAccountType.EXTERNAL) or not maybe_sender_transfer_account)): response_object = { 'message': 'Only external users involved in this transfer', 'data': {} } # Case 4: One or both of the transfer accounts are affiliated with Sempo accounts. # This is the only case where we want to generate a new CreditTransfer object. else: send_transfer_account = create_transfer_account_if_required( sender_blockchain_address, token, TransferAccountType.EXTERNAL) receive_transfer_account = create_transfer_account_if_required( recipient_blockchain_address, token, TransferAccountType.EXTERNAL) transfer = CreditTransfer( transfer_amount, token=token, sender_transfer_account=send_transfer_account, recipient_transfer_account=receive_transfer_account, transfer_type=TransferTypeEnum.PAYMENT, sender_user=maybe_sender_user, recipient_user=maybe_recipient_user, require_sufficient_balance=False, received_third_party_sync=True) transfer.resolve_as_complete_with_existing_blockchain_transaction( blockchain_transaction_hash) db.session.flush() credit_transfer = credit_transfer_schema.dump(transfer).data response_object = { 'message': 'Transfer Successful', 'data': { 'credit_transfer': credit_transfer, } } return make_response(jsonify(response_object)), 201