swap works again reliably
This commit is contained in:
parent
291a5fed2c
commit
eebba5d6b4
@ -22,8 +22,8 @@ DEVELOPER_CHAT_ID="777826553"
|
|||||||
TELEGRAM_BOT_TOKEN="6749075936:AAHUHiPTDEIu6JH7S2fQdibwsu6JVG3FNG0"
|
TELEGRAM_BOT_TOKEN="6749075936:AAHUHiPTDEIu6JH7S2fQdibwsu6JVG3FNG0"
|
||||||
|
|
||||||
DISPLAY_CURRENCY=USD
|
DISPLAY_CURRENCY=USD
|
||||||
FOLLOW_AMOUNT=3
|
#FOLLOW_AMOUNT=3
|
||||||
#FOLLOW_AMOUNT=percentage
|
FOLLOW_AMOUNT=percentage
|
||||||
|
|
||||||
LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112
|
LIQUIDITY_TOKENS=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,So11111111111111111111111111111111111111112
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import base64
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@ -50,7 +51,7 @@ from datetime import datetime
|
|||||||
from solana.rpc.types import TokenAccountOpts, TxOpts
|
from solana.rpc.types import TokenAccountOpts, TxOpts
|
||||||
from typing import List, Dict, Any, Tuple
|
from typing import List, Dict, Any, Tuple
|
||||||
import traceback
|
import traceback
|
||||||
import base64
|
import httpx
|
||||||
|
|
||||||
# # # solders/solana libs (solana_client) # # #
|
# # # solders/solana libs (solana_client) # # #
|
||||||
from spl.token._layouts import MINT_LAYOUT
|
from spl.token._layouts import MINT_LAYOUT
|
||||||
@ -234,6 +235,17 @@ class SolanaWS:
|
|||||||
class SolanaAPI:
|
class SolanaAPI:
|
||||||
|
|
||||||
pk = None
|
pk = None
|
||||||
|
|
||||||
|
ENDPOINT_APIS_URL = {
|
||||||
|
"QUOTE": "https://quote-api.jup.ag/v6/quote?",
|
||||||
|
"SWAP": "https://quote-api.jup.ag/v6/swap",
|
||||||
|
"OPEN_ORDER": "https://jup.ag/api/limit/v1/createOrder",
|
||||||
|
"CANCEL_ORDERS": "https://jup.ag/api/limit/v1/cancelOrders",
|
||||||
|
"QUERY_OPEN_ORDERS": "https://jup.ag/api/limit/v1/openOrders?wallet=",
|
||||||
|
"QUERY_ORDER_HISTORY": "https://jup.ag/api/limit/v1/orderHistory",
|
||||||
|
"QUERY_TRADE_HISTORY": "https://jup.ag/api/limit/v1/tradeHistory"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, process_transaction_callback = None, on_initial_subscription_callback = None, on_bot_message=None):
|
def __init__(self, process_transaction_callback = None, on_initial_subscription_callback = None, on_bot_message=None):
|
||||||
self.process_transaction = process_transaction_callback
|
self.process_transaction = process_transaction_callback
|
||||||
self.on_initial_subscription = on_initial_subscription_callback
|
self.on_initial_subscription = on_initial_subscription_callback
|
||||||
@ -247,6 +259,7 @@ class SolanaAPI:
|
|||||||
self.dex = DEX
|
self.dex = DEX
|
||||||
self.solana_ws = SolanaWS(on_message=self.process_transaction)
|
self.solana_ws = SolanaWS(on_message=self.process_transaction)
|
||||||
|
|
||||||
|
|
||||||
async def process_messages(self, solana_ws):
|
async def process_messages(self, solana_ws):
|
||||||
while True:
|
while True:
|
||||||
message = await solana_ws.message_queue.get()
|
message = await solana_ws.message_queue.get()
|
||||||
@ -587,7 +600,7 @@ class SolanaAPI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching transaction details: {e}")
|
logging.error(f"Error fetching transaction details: {e}")
|
||||||
|
|
||||||
logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id}. retry in {retry_delay} s.")
|
logging.info(f"({_} of {max_retries}) Waiting for transaction details for {transaction_id} [ retry in {retry_delay} s.]")
|
||||||
await asyncio.sleep(retry_delay)
|
await asyncio.sleep(retry_delay)
|
||||||
if backoff:
|
if backoff:
|
||||||
retry_delay = retry_delay * 1.2
|
retry_delay = retry_delay * 1.2
|
||||||
@ -672,6 +685,100 @@ class SolanaAPI:
|
|||||||
logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)} \r\n {e}")
|
logging.error(f"Error getting balance for {token_address} in {wallet_address}: {str(e)} \r\n {e}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def swap_on_jupiter(
|
||||||
|
self,
|
||||||
|
input_mint: str,
|
||||||
|
output_mint: str,
|
||||||
|
amount: int,
|
||||||
|
slippage_bps: int = 1,
|
||||||
|
swap_mode: str = "ExactIn",
|
||||||
|
priority_fee: int = 0,
|
||||||
|
only_direct_routes: bool = False,
|
||||||
|
as_legacy_transaction: bool = False,
|
||||||
|
exclude_dexes: list = None,
|
||||||
|
max_accounts: int = None
|
||||||
|
) -> str:
|
||||||
|
"""Perform a swap on Jupiter with an option to set priority.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_mint (str): Input token mint.
|
||||||
|
output_mint (str): Output token mint.
|
||||||
|
amount (int): Amount to swap, considering token decimals.
|
||||||
|
slippage_bps (int): Slippage in basis points.
|
||||||
|
swap_mode (str): Swap mode, either 'ExactIn' or 'ExactOut'.
|
||||||
|
priority_level (int): Priority level for the transaction fee.
|
||||||
|
only_direct_routes (bool): Limit to direct routes only.
|
||||||
|
as_legacy_transaction (bool): Use legacy transaction format.
|
||||||
|
exclude_dexes (list): List of DEXes to exclude.
|
||||||
|
max_accounts (int): Max number of accounts involved.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Serialized transaction data for the swap.
|
||||||
|
"""
|
||||||
|
# Get a quote from Jupiter
|
||||||
|
quote_url = self.ENDPOINT_APIS_URL['QUOTE'] + "inputMint=" + input_mint + "&outputMint=" + output_mint + "&amount=" + str(amount) + "&swapMode=" + swap_mode + "&onlyDirectRoutes=" + str(only_direct_routes).lower() + "&asLegacyTransaction=" + str(as_legacy_transaction).lower()
|
||||||
|
if slippage_bps:
|
||||||
|
quote_url += "&slippageBps=" + str(slippage_bps)
|
||||||
|
if exclude_dexes:
|
||||||
|
quote_url += "&excludeDexes=" + ','.join(exclude_dexes).lower()
|
||||||
|
if max_accounts:
|
||||||
|
quote_url += "&maxAccounts=" + str(max_accounts)
|
||||||
|
|
||||||
|
quote_response = httpx.get(url=quote_url).json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
quote_response['routePlan']
|
||||||
|
except:
|
||||||
|
raise Exception(quote_response['error'])
|
||||||
|
|
||||||
|
|
||||||
|
# Prepare transaction parameters
|
||||||
|
fees = "auto" if priority_fee == 0 else priority_fee
|
||||||
|
|
||||||
|
pair = Keypair.from_bytes(base58.b58decode(self.pk))
|
||||||
|
pubK = pair.pubkey().__str__()
|
||||||
|
transaction_parameters = {
|
||||||
|
"quoteResponse":quote_response,
|
||||||
|
"userPublicKey": pubK,
|
||||||
|
"wrapAndUnwrapSol": True,
|
||||||
|
"computeUnitPriceMicroLamports":fees
|
||||||
|
}
|
||||||
|
# This will raise an error if data isn't JSON serializable
|
||||||
|
json_string = json.dumps(transaction_parameters)
|
||||||
|
validated_data = json.loads(json_string)
|
||||||
|
|
||||||
|
response = httpx.post(url=self.ENDPOINT_APIS_URL['SWAP'], json=validated_data)
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
|
# headers = {
|
||||||
|
# 'Content-Type': 'application/json',
|
||||||
|
# 'Accept': 'application/json'
|
||||||
|
# }
|
||||||
|
# response = requests.request("POST", self.ENDPOINT_APIS_URL['SWAP'], headers=headers, data=validated_data)
|
||||||
|
# response_data = response.json()
|
||||||
|
|
||||||
|
# result = response_data['swapTransaction']
|
||||||
|
|
||||||
|
# # # Send the swap request to Jupiter
|
||||||
|
# async with httpx.AsyncClient() as client:
|
||||||
|
# response = await client.post(
|
||||||
|
# self.ENDPOINT_APIS_URL['SWAP'],
|
||||||
|
# json=validated_data
|
||||||
|
# )
|
||||||
|
# response_data = response.json()
|
||||||
|
|
||||||
|
result = response_data['swapTransaction']
|
||||||
|
try:
|
||||||
|
response_data['swapTransaction']
|
||||||
|
return response_data['swapTransaction']
|
||||||
|
except:
|
||||||
|
raise Exception(response_data['error'])
|
||||||
|
# Return the serialized transaction
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def follow_move(self,move):
|
async def follow_move(self,move):
|
||||||
try:
|
try:
|
||||||
your_balances = await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
|
your_balances = await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
|
||||||
@ -756,101 +863,27 @@ class SolanaAPI:
|
|||||||
|
|
||||||
if self.pk is None:
|
if self.pk is None:
|
||||||
self.pk = await get_pk()
|
self.pk = await get_pk()
|
||||||
for retry in range(1):
|
for retry in range(2):
|
||||||
try:
|
try:
|
||||||
private_key = Keypair.from_bytes(base58.b58decode(self.pk))
|
private_key = Keypair.from_bytes(base58.b58decode(self.pk))
|
||||||
async_client = AsyncClient(SOLANA_WS_URL)
|
async_client = AsyncClient(SOLANA_WS_URL)
|
||||||
jupiter = Jupiter(async_client, private_key)
|
jupiter = Jupiter(async_client, private_key)
|
||||||
transaction_data = await jupiter.swap(
|
|
||||||
|
# https://station.jup.ag/api-v6/post-swap
|
||||||
|
#transaction_data = await jupiter.swap(
|
||||||
|
transaction_data = await self.swap_on_jupiter(
|
||||||
input_mint=move['token_in'],
|
input_mint=move['token_in'],
|
||||||
output_mint=move['token_out'],
|
output_mint=move['token_out'],
|
||||||
amount=amount_lamports,
|
amount=amount_lamports,
|
||||||
slippage_bps=300, # Increased to 3%
|
slippage_bps=300, # Increased to 3%
|
||||||
|
#priority_fee= 100_000 commented for auto
|
||||||
)
|
)
|
||||||
logging.info(f"Initiating move. Transaction data:\n {transaction_data}")
|
logging.info(f"Initiating move. Transaction data:\n {transaction_data}")
|
||||||
|
|
||||||
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
|
raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data))
|
||||||
fee = await async_client.get_fee_for_message(raw_transaction.message)
|
|
||||||
priority_fee = fee.value or 100_000
|
|
||||||
|
|
||||||
|
|
||||||
# fee = await async_client.get_fee_for_message(transaction_data)
|
|
||||||
# priority_fee = 0
|
|
||||||
# if PRIORITY:
|
|
||||||
# priority_fee = 100 * PRIORITY # defalt if we can't get current rate
|
|
||||||
# try:
|
|
||||||
# priority_fee = await calculate_priority_fee(async_client, PRIORITY)
|
|
||||||
# except:
|
|
||||||
# logging.warning(f"Failed to get priority fee. Using default value: {priority_fee}")
|
|
||||||
|
|
||||||
# error_logger.info(f"Initiating move. Transaction data:\n {transaction_data}")
|
|
||||||
# message = raw_transaction.message
|
|
||||||
|
|
||||||
# Add compute budget instruction to set priority fee
|
|
||||||
# from solders.compute_budget import set_compute_unit_price
|
|
||||||
# compute_budget_instruction = set_compute_unit_price(priority_fee)
|
|
||||||
|
|
||||||
# Add compute budget instruction to the transaction
|
|
||||||
# raw_transaction.message.add_instruction(compute_budget_instruction)
|
|
||||||
|
|
||||||
# Create new instructions list with compute budget instruction first
|
|
||||||
# new_instructions = [compute_budget_instruction] + list(raw_transaction.message.instructions)
|
|
||||||
|
|
||||||
# # Create a new message with the updated instructions
|
|
||||||
# from solders.message import MessageV0
|
|
||||||
# new_message = MessageV0(
|
|
||||||
# instructions=new_instructions,
|
|
||||||
# address_table_lookups=raw_transaction.message.address_table_lookups,
|
|
||||||
# recent_blockhash=raw_transaction.message.recent_blockhash,
|
|
||||||
# payer=raw_transaction.message.payer
|
|
||||||
# )
|
|
||||||
|
|
||||||
# working - no priority fee
|
# working - no priority fee
|
||||||
signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message))
|
signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message))
|
||||||
signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature])
|
signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature])
|
||||||
# # # # # # # # # # # # # # # # # #
|
|
||||||
# new - not working
|
|
||||||
# signature = private_key.sign_message(new_message.to_bytes_versioned())
|
|
||||||
# signed_txn = VersionedTransaction.populate(new_message, [signature])
|
|
||||||
# from solders.compute_budget import set_compute_unit_price, ID as COMPUTE_BUDGET_ID
|
|
||||||
|
|
||||||
# priority_fee_ix = set_compute_unit_price(priority_fee)
|
|
||||||
|
|
||||||
# # Get the current message
|
|
||||||
# msg = raw_transaction.message
|
|
||||||
|
|
||||||
# new_account_keys = msg.account_keys
|
|
||||||
# program_id_index = 0
|
|
||||||
# if COMPUTE_BUDGET_ID not in msg.account_keys:
|
|
||||||
# new_account_keys = msg.account_keys + [COMPUTE_BUDGET_ID]
|
|
||||||
# program_id_index = len(msg.account_keys) # Index of the newly added program ID
|
|
||||||
# else:
|
|
||||||
# new_account_keys = msg.account_keys
|
|
||||||
# program_id_index = msg.account_keys.index(COMPUTE_BUDGET_ID)
|
|
||||||
|
|
||||||
# # Compile the priority fee instruction
|
|
||||||
# compiled_priority_fee_ix = CompiledInstruction(
|
|
||||||
# program_id_index=program_id_index,
|
|
||||||
# accounts=bytes([]),
|
|
||||||
# data=priority_fee_ix.data
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Add priority fee instruction at the beginning
|
|
||||||
# new_instructions = [compiled_priority_fee_ix] + msg.instructions
|
|
||||||
|
|
||||||
# # Create new message with updated instructions
|
|
||||||
# new_message = Message.new_with_compiled_instructions(
|
|
||||||
# num_required_signatures=msg.header.num_required_signatures,
|
|
||||||
# num_readonly_signed_accounts=msg.header.num_readonly_signed_accounts,
|
|
||||||
# num_readonly_unsigned_accounts=msg.header.num_readonly_unsigned_accounts,
|
|
||||||
# account_keys=new_account_keys,
|
|
||||||
# recent_blockhash=msg.recent_blockhash,
|
|
||||||
# instructions=new_instructions
|
|
||||||
# )
|
|
||||||
|
|
||||||
# signature = private_key.sign_message(message.to_bytes_versioned(new_message))
|
|
||||||
# signed_txn = VersionedTransaction.populate(new_message, [signature])
|
|
||||||
# # # # # # # # # # # # # # # # # #
|
|
||||||
|
|
||||||
opts = TxOpts(
|
opts = TxOpts(
|
||||||
skip_preflight=False,
|
skip_preflight=False,
|
||||||
@ -861,7 +894,7 @@ class SolanaAPI:
|
|||||||
result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts)
|
result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts)
|
||||||
|
|
||||||
transaction_id = json.loads(result.to_json())['result']
|
transaction_id = json.loads(result.to_json())['result']
|
||||||
notification = f"Follow Transaction Sent:\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'>swapping {amount_to_swap:.2f} {token_name_in}</a>"
|
notification = f"Follow {move.get('type', 'SWAP').upper()} Success:\n<b>Transaction:</b> <a href='https://solscan.io/tx/{transaction_id}'> swapping {amount_to_swap:.2f} {token_name_in}</a>"
|
||||||
logging.info(notification)
|
logging.info(notification)
|
||||||
await telegram_utils.send_telegram_message(notification)
|
await telegram_utils.send_telegram_message(notification)
|
||||||
tx_details = await SAPI.get_transaction_details_with_retry(transaction_id, retry_delay=5, max_retries=10, backoff=False)
|
tx_details = await SAPI.get_transaction_details_with_retry(transaction_id, retry_delay=5, max_retries=10, backoff=False)
|
||||||
@ -879,7 +912,7 @@ class SolanaAPI:
|
|||||||
# error_logger.error(error_message)
|
# error_logger.error(error_message)
|
||||||
# error_logger.exception(e)
|
# error_logger.exception(e)
|
||||||
await telegram_utils.send_telegram_message(error_message)
|
await telegram_utils.send_telegram_message(error_message)
|
||||||
amount = int(amount * 0.9)
|
amount_to_swap = int(amount_to_swap * 0.9)
|
||||||
|
|
||||||
await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
|
await DEX.get_wallet_balances(YOUR_WALLET, doGetTokenName=False)
|
||||||
|
|
||||||
@ -922,19 +955,20 @@ class SolanaAPI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error following move: {e}")
|
logging.error(f"Error following move: {e}")
|
||||||
|
|
||||||
async def calculate_priority_fee(async_client, priority_level=5):
|
async def calculate_priority_fee(self, async_client, priority_level=5):
|
||||||
|
try:
|
||||||
recent_fees = await async_client.get_recent_prioritization_fees()
|
recent_fees = await async_client.get_recent_prioritization_fees()
|
||||||
|
|
||||||
if not recent_fees:
|
if not recent_fees or len(recent_fees) == 0:
|
||||||
return 1000 # fallback value in microlamports
|
return 100_000 # fallback value in microlamports
|
||||||
|
|
||||||
# Calculate average and max fees
|
# Calculate average and max fees
|
||||||
fees = [fee.prioritization_fee for fee in recent_fees]
|
fees = [slot_fee.prioritization_fee for slot_fee in recent_fees]
|
||||||
avg_fee = sum(fees) / len(fees)
|
avg_fee = sum(fees) / len(fees)
|
||||||
max_fee = max(fees)
|
max_fee = max(fees)
|
||||||
|
|
||||||
# Calculate base fee (weighted average between mean and max)
|
# Calculate base fee (weighted average between mean and max)
|
||||||
base_fee = (2 * avg_fee + max_fee) / 3 # You can adjust this weighting
|
base_fee = (2 * avg_fee + max_fee) / 3
|
||||||
|
|
||||||
# Calculate scaling factor (priority_level / 5)
|
# Calculate scaling factor (priority_level / 5)
|
||||||
# priority 5 = 1x base_fee
|
# priority 5 = 1x base_fee
|
||||||
@ -945,8 +979,11 @@ class SolanaAPI:
|
|||||||
final_fee = int(base_fee * scaling_factor)
|
final_fee = int(base_fee * scaling_factor)
|
||||||
|
|
||||||
# Set minimum fee to avoid too low values
|
# Set minimum fee to avoid too low values
|
||||||
return max(final_fee, 100) # minimum 100 microlamports
|
return max(final_fee, 100_001) # minimum 100,000 microlamports
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"Error calculating priority fee: {str(e)}")
|
||||||
|
return 100_000 # fallback value in microlamports
|
||||||
|
|
||||||
class SolanaDEX:
|
class SolanaDEX:
|
||||||
def __init__(self, DISPLAY_CURRENCY: str):
|
def __init__(self, DISPLAY_CURRENCY: str):
|
||||||
|
@ -10,7 +10,7 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log
|
|||||||
import secrets
|
import secrets
|
||||||
import json
|
import json
|
||||||
# from crypto.sol.config import LIQUIDITY_TOKENS
|
# from crypto.sol.config import LIQUIDITY_TOKENS
|
||||||
from config import LIQUIDITY_TOKENS
|
from config import LIQUIDITY_TOKENS, YOUR_WALLET
|
||||||
|
|
||||||
from modules import storage, utils, SolanaAPI
|
from modules import storage, utils, SolanaAPI
|
||||||
from modules.utils import async_safe_call, decode_instruction_data
|
from modules.utils import async_safe_call, decode_instruction_data
|
||||||
@ -24,6 +24,8 @@ def init_app(tr_handler=None):
|
|||||||
global on_transaction
|
global on_transaction
|
||||||
on_transaction = tr_handler
|
on_transaction = tr_handler
|
||||||
app = Flask(__name__, template_folder='../templates', static_folder='../static')
|
app = Flask(__name__, template_folder='../templates', static_folder='../static')
|
||||||
|
|
||||||
|
app.secret_key = secrets.token_hex(16)
|
||||||
executor = ThreadPoolExecutor(max_workers=10) # Adjust the number of workers as needed
|
executor = ThreadPoolExecutor(max_workers=10) # Adjust the number of workers as needed
|
||||||
login_manager = LoginManager(app)
|
login_manager = LoginManager(app)
|
||||||
login_manager.login_view = 'login'
|
login_manager.login_view = 'login'
|
||||||
@ -423,6 +425,11 @@ def init_app(tr_handler=None):
|
|||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@login_manager.unauthorized_handler
|
||||||
|
def unauthorized():
|
||||||
|
return redirect('/login?next=' + request.path)
|
||||||
|
# return jsonify({'error': 'Unauthorized'}), 401
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -460,12 +467,14 @@ def init_app(tr_handler=None):
|
|||||||
|
|
||||||
@app.route('/wallet/<int:wallet_id>/transactions', methods=['GET'])
|
@app.route('/wallet/<int:wallet_id>/transactions', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@login_required
|
||||||
def get_transactions(wallet_id):
|
def get_transactions(wallet_id):
|
||||||
transactions = storage.get_transactions(wallet_id)
|
transactions = storage.get_transactions(wallet_id)
|
||||||
return jsonify(transactions)
|
return jsonify(transactions)
|
||||||
|
|
||||||
@app.route('/wallet/<int:wallet_id>/holdings', methods=['GET'])
|
@app.route('/wallet/<int:wallet_id>/holdings', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@login_required
|
||||||
def get_holdings(wallet_id):
|
def get_holdings(wallet_id):
|
||||||
holdings = storage.get_holdings(wallet_id)
|
holdings = storage.get_holdings(wallet_id)
|
||||||
return jsonify(holdings)
|
return jsonify(holdings)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user