@@ -46,36 +46,47 @@ def __init__(self, position_key: str, owner_wallet_address: str, owner_wallet_ke
46
46
self .stable_token = BEP20Token (self .vault .stableTokenAddress (), self .w3_provider )
47
47
self .asset_token = BEP20Token (self .vault .assetTokenAddress (), self .w3_provider )
48
48
49
+ # If True, enables the allowance verification and automatic approval of tokens before transactions
50
+ self .auto_token_approval = False
51
+
52
+ # Specify custom web3 transaction gasPrice parameter for the BSC transactions
53
+ self .gasPrice = 5000000000
54
+
49
55
""" ------------------ Transactional Methods (Requires private wallet key) ------------------ """
50
56
51
- def do_invest (self , stable_token_amt : int = 0 , asset_token_amt : int = 0 , _approve = False ) -> TransactionReceipt :
57
+ def do_invest (self , stable_token_amt : int = 0 , asset_token_amt : int = 0 ) -> TransactionReceipt :
52
58
"""
53
59
Invest the specified amount of each token into the Automated Vault.
54
60
Use self.asset_token and self.stable_token to identify the underlying assets.
55
61
56
62
:param stable_token_amt: The amount of stable token to deposit
57
63
:param asset_token_amt: The amount of asset token to deposit
58
- :param _approve: If True, approves the deposit token for spending by the vault token
59
- :return:
64
+
65
+ :return: TransactionReceipt object
60
66
"""
61
67
assert stable_token_amt > 0 or asset_token_amt > 0 , \
62
68
"Please provide an investment value for either the stable or asset tokens"
63
69
70
+ txn_nonce = self ._get_nonce ()
71
+
64
72
# Ensure that allowances match desired investment amount
65
73
if stable_token_amt > 0 :
66
74
token_bal = self .stable_token .balanceOf (self .owner_address )
67
75
assert token_bal >= stable_token_amt , \
68
76
f"Insufficient funds to invest { stable_token_amt } { self .stable_token .symbol ()} ({ token_bal } Owned)"
69
- if _approve :
70
- self .do_approve_token (self .stable_token )
77
+ if self .auto_token_approval :
78
+ if self .do_approve_token (self .stable_token ) is not None :
79
+ txn_nonce += 1
71
80
if asset_token_amt > 0 :
72
81
token_bal = self .asset_token .balanceOf (self .owner_address )
73
82
assert token_bal >= asset_token_amt , \
74
83
f"Insufficient funds to invest { asset_token_amt } { self .asset_token .symbol ()} ({ token_bal } Owned)"
75
- if _approve :
76
- self .do_approve_token (self .asset_token )
84
+ if self .auto_token_approval :
85
+ if self .do_approve_token (self .asset_token ) is not None :
86
+ txn_nonce += 1
77
87
78
- return self ._execute (self .vault .invest (stable_token_amt , asset_token_amt , shareReceiver = self .owner_address ))
88
+ return self ._execute (self .vault .invest (stable_token_amt , asset_token_amt , shareReceiver = self .owner_address ),
89
+ _nonce = txn_nonce )
79
90
80
91
def do_withdraw (self , shares : int , pct_stable : float = None , strategy : str = "Minimize Trading" ) -> TransactionReceipt :
81
92
"""
@@ -89,56 +100,90 @@ def do_withdraw(self, shares: int, pct_stable: float = None, strategy: str = "Mi
89
100
assert self .shares ()[0 ] >= shares , f"Shares owned insufficient to withdraw { shares } " \
90
101
f"({ self .from_wei (shares , self .bep20_vault_token .decimals ())} ) shares"
91
102
103
+ txn_nonce = self ._get_nonce ()
104
+
105
+ # Process withdraw functions according to the strategy passed:
92
106
if strategy .lower () == "minimize trading" :
93
- # assert self.bep20_vault_token.allowance(self.owner_address, self.vault.address) >= shares, \
94
- # f"Insufficient approval amount - Spender ({self.vault.address}) requires an allowance of {shares} " \
95
- # f"{self.bep20_vault_token.symbol()} ({self.bep20_vault_token.address})"
107
+ if self .auto_token_approval :
108
+ if self .do_approve_token (self .bep20_vault_token , _spender = self .vault .address , _min_amount = shares ) is not None :
109
+ txn_nonce += 1
110
+ else :
111
+ assert self .bep20_vault_token .allowance (self .owner_address , self .vault .address ) >= shares , \
112
+ f"Insufficient approval amount - Spender ({ self .vault .address } ) requires an allowance of { shares } " \
113
+ f"{ self .bep20_vault_token .symbol ()} ({ self .bep20_vault_token .address } )"
114
+
115
+ return self ._execute (self .vault .withdraw (shares ), _nonce = txn_nonce )
96
116
97
- return self ._execute (self .vault .withdraw (shares ))
98
117
elif strategy .lower () == "convert all" :
99
118
assert pct_stable is not None , "Please provide a stable token percentage to determine token swap"
100
- assert self .bep20_vault_token .allowance (self .owner_address , self .gateway .address ) >= shares , \
101
- f"Insufficient approval amount - Spender ({ self .gateway .address } ) requires an allowance of { shares } " \
102
- f"{ self .bep20_vault_token .symbol ()} ({ self .bep20_vault_token .address } )"
103
119
104
- assert 0.0 < pct_stable <= 1.0 , "Invalid value for pct_stable parameter, must follow 0.0 < pct_stable <= 1.0"
120
+ if self .auto_token_approval :
121
+ if self .do_approve_token (self .bep20_vault_token , _spender = self .gateway .address , _min_amount = shares ) is not None :
122
+ txn_nonce += 1
123
+ else :
124
+ assert self .bep20_vault_token .allowance (self .owner_address , self .gateway .address ) >= shares , \
125
+ f"Insufficient approval amount - Spender ({ self .gateway .address } ) requires an allowance of { shares } " \
126
+ f"{ self .bep20_vault_token .symbol ()} ({ self .bep20_vault_token .address } )"
127
+
128
+ assert 0.0 <= pct_stable <= 1.0 , "Invalid value for pct_stable parameter, must follow 0.0 <= pct_stable <= 1.0"
105
129
106
130
stable_return_bps = floor (pct_stable * 10000 )
107
131
108
- return self ._execute (self .gateway .withdraw (shares , stableReturnBps = stable_return_bps ))
132
+ return self ._execute (self .gateway .withdraw (shares , stableReturnBps = stable_return_bps ), _nonce = txn_nonce )
133
+
109
134
else :
110
135
raise ValueError ("Invalid strategy - Options are 'Minimize Trading' or 'Convert All'" )
111
136
112
137
def do_close (self , pct_stable : float = None , strategy : str = "Minimize Trading" ) -> TransactionReceipt :
113
138
"""
114
139
Withdraws all outstanding shares from the pool and closes position.
115
140
116
- :param pct_stable: See self.withdraw ()
117
- :param strategy: See self.withdraw ()
141
+ :param pct_stable: See self.do_withdraw ()
142
+ :param strategy: See self.do_withdraw ()
118
143
"""
119
- return self . do_withdraw ( shares = self .shares ()[0 ], pct_stable = pct_stable , strategy = strategy )
144
+ shares = self .shares ()[0 ]
120
145
121
- def do_approve_token (self , token : Union [BEP20Token , str ], amount : int = None , _spender : str = None ) -> TransactionReceipt :
146
+ assert shares > 0 , f"Cannot close position with a balance of { shares } shares."
147
+
148
+ return self .do_withdraw (shares = shares , pct_stable = pct_stable , strategy = strategy )
149
+
150
+ def do_approve_token (self , token : Union [BEP20Token , str ], amount : int = None , _min_amount : int = None ,
151
+ _spender : str = None ) -> Union [TransactionReceipt , None ]:
122
152
"""
123
153
Approves the given token for usage by the Automated Vault.
124
154
125
155
:param token: Optional - either the BEP20Token object, or the token address (str)
126
156
:param amount: The amount of token to approve, default = maximum
127
157
:param _spender: (Should not be changed by the caller) The address to give token spending access to
158
+ :param _min_amount: Used for internal functions when checking to see if a token has the minimum approval requirement
159
+
160
+ :return:
161
+ If the current token allowance already meets or exceeds the given amount:
162
+ - None
163
+ Else if the current token allowance does not meet the given amount:
164
+ - TransactionReceipt object
128
165
"""
129
166
if type (token ) != BEP20Token :
130
167
token = BEP20Token (token )
131
168
169
+ print (f"Approving { token .symbol ()} ..." )
170
+
132
171
if amount is None :
133
172
# Use maximum approval amount if no amount is specified
134
173
amount = 2 ** 256 - 1
135
174
136
175
if _spender is None :
137
176
_spender = self .address
138
177
139
- func_call = token .prepare_approve (checksum (_spender ), amount )
178
+ if token .allowance (self .owner_address , _spender ) >= (amount if _min_amount is None else _min_amount ):
179
+ print ("Approval canceled - allowance already exceeds amount" )
180
+ return None
140
181
141
- return self ._execute (func_call )
182
+ txn = self ._execute (token .prepare_approve (checksum (_spender ), amount ))
183
+
184
+ print (f"Approved { 'MAX' if amount == (2 ** 256 - 1 ) else amount } { token .symbol ()} for contract { _spender } ." )
185
+
186
+ return txn
142
187
143
188
144
189
""" ---------------- Informational Methods (Private wallet key not required) ---------------- """
@@ -187,6 +232,14 @@ def get_vault_summary(self, position_key: str = None) -> AttrDict:
187
232
for vault in r .json ()["data" ]["strategyPools" ]:
188
233
if vault ["key" ].lower () == position_key :
189
234
return AttrDict (vault )
235
+ else :
236
+ try :
237
+ if vault ["iuToken" ]["symbol" ].lower () == position_key :
238
+ print (f"Warning: Had to use iuToken to match vault data instead of key (Key = { vault ['key' ]} )" )
239
+ return AttrDict (vault )
240
+ except KeyError :
241
+ # In the event that the vault data did not have an iuToken key for some reason
242
+ pass
190
243
else :
191
244
raise ValueError (f"Could not locate a vault with the key { self .key } " )
192
245
@@ -261,7 +314,7 @@ def cost_basis(self) -> float: # tuple[float, float]
261
314
262
315
""" -------------------------------- Utility Methods -------------------------------- """
263
316
264
- def _execute (self , function_call : web3 .contract .ContractFunction ) -> TransactionReceipt :
317
+ def _execute (self , function_call : web3 .contract .ContractFunction , _nonce : int = None ) -> Union [ TransactionReceipt , AttrDict ] :
265
318
"""
266
319
:param function_call: The uncalled and prepared contract method to sign and send
267
320
"""
@@ -272,8 +325,8 @@ def _execute(self, function_call: web3.contract.ContractFunction) -> Transaction
272
325
"from" : self .owner_address ,
273
326
'chainId' : 56 , # 56: BSC mainnet
274
327
# 'gas': 76335152,
275
- 'gasPrice' : 5000000000 ,
276
- 'nonce' : self .w3_provider . eth . get_transaction_count ( self . owner_address ) ,
328
+ 'gasPrice' : self . gasPrice , # 5000000000
329
+ 'nonce' : self ._get_nonce () if _nonce is None else _nonce ,
277
330
})
278
331
# print(txn)
279
332
@@ -289,7 +342,10 @@ def _execute(self, function_call: web3.contract.ContractFunction) -> Transaction
289
342
except Exception as exc :
290
343
print (f"COULD NOT BUILD TRANSACTION RECEIPT OBJECT - { exc } " )
291
344
# Catch case to prevent receipt from being lost if TransactionReceipt object somehow can't be built
292
- return receipt
345
+ return AttrDict (receipt )
346
+
347
+ def _get_nonce (self ) -> int :
348
+ return self .w3_provider .eth .get_transaction_count (self .owner_address )
293
349
294
350
@staticmethod
295
351
def to_wei (amt : float , decimals : int ) -> int :
@@ -316,8 +372,10 @@ def _decode_withdraw_transaction(self, transaction_address: Optional):
316
372
317
373
try :
318
374
decoded_bank_transaction = self .vault .contract .decode_function_input (transaction .input )
375
+ print ("withdraw on vault" )
319
376
except :
320
377
decoded_bank_transaction = self .gateway .contract .decode_function_input (transaction .input )
378
+ print ("withdraw on gateway" )
321
379
322
380
decoded_calldata = decode_abi (
323
381
['uint256' , 'uint256' ],
@@ -339,12 +397,14 @@ def _decode_deposit_transaction(self, transaction_address: Optional):
339
397
)
340
398
"""
341
399
transaction = self .w3_provider .eth .get_transaction (transaction_address )
342
- # print(transaction)
400
+ print (transaction )
343
401
344
402
try :
345
403
decoded_bank_transaction = self .vault .contract .decode_function_input (transaction .input )
404
+ print ("deposit on vault" )
346
405
except :
347
406
decoded_bank_transaction = self .gateway .contract .decode_function_input (transaction .input )
407
+ print ("deposit on gateway" )
348
408
349
409
decoded_calldata = decode_abi (
350
410
["uint256" ],
0 commit comments