Okay guys, so, that's not quite what I wanted to handle next, but I currently have a bit of a problem with the Sunny Aggregator platform: I have some funds on the pools there, which I would like to withdraw, but due to a nastly issue with incorrect handling of the “point” in the values that you will enter on the website, you basically cannot withdraw any value with decimals: for instance you can withdraw “7” LPs from a given pool, but not “7.” or “7.0”, or “7.78”, etc.
And I guess the dev team is going its best, but that still a bit too slow from my perspective. And on top of that, I've been waiting to jump into solana programming since a long time now, so that sounds like a good opportunity to give it a try here 😊!
program_id
), providing an array of accounts and the instruction_data"""Module for SunnyAg class definition""" import logging from nvp.nvp_context import NVPContext from nvp.nvp_component import NVPComponent logger = logging.getLogger(__name__) class SunnyAg(NVPComponent): """SunnyAg component class""" def __init__(self, ctx: NVPContext): """class constructor""" NVPComponent.__init__(self, ctx) def process_command(self, cmd): """Check if this component can process the given command""" if cmd == 'withdraw': logger.info("Should withdraw funds here.") return True return False if __name__ == "__main__": # Create the context: context = NVPContext() # Add our component: comp = context.register_component("SunnyAg", SunnyAg(context)) context.define_subparsers("main", { 'withdraw': None, }) comp.run()
"solana_env": { "packages": ["requests", "jstyleson", "xxhash", "solana", "numpy"] }
"sunnyag": { "custom_python_env": "solana_env", "cmd": "${PYTHON} ${PROJECT_ROOT_DIR}/nvh/crypto/sunny_ag.py", "python_path": ["${PROJECT_ROOT_DIR}", "${NVP_ROOT_DIR}"] }
$ nvp sunnyag withdraw 2022/05/19 19:42:48 [__main__] INFO: Should withdraw funds here.
"""Module for SunnyAg class definition""" import logging from getpass import getpass from solana.transaction import AccountMeta, Transaction, TransactionInstruction from solana.rpc.types import TxOpts from solana.rpc.api import Client from solana.publickey import PublicKey from solana.keypair import Keypair # from solana.account import Account # from solana.rpc.commitment import Recent, Root # from solana.system_program import create_account, CreateAccountParams # from spl.token.instructions import set_authority, SetAuthorityParams, AuthorityType import base58 from nvp.nvp_context import NVPContext from nvp.nvp_component import NVPComponent logger = logging.getLogger(__name__) class SunnyAg(NVPComponent): """SunnyAg component class""" def __init__(self, ctx: NVPContext): """class constructor""" NVPComponent.__init__(self, ctx) def withdraw_funds(self): """method used to withdraw our funds from a given pool.""" url = 'https://api.mainnet-beta.solana.com' client = Client(url) pwd = getpass('Chrome -> Phantom -> Settings -> Export private Key -> paste it here: ') # cf. https://solscan.io/tx/MUHWqsXtxQnmFzKE5UNSvfCDiikoihXW64Z9bVcYBsi7rBsML2HNXsaXPtxon7tRvSL2RLFgWfXj2DYVrvhoyTt # as reference for the setup below: # id of the program: program = 'SPQR4kT3q2oUKEJes2L6NNSBCiPW9SfuhkuqC9bp6Sx' # get int based keypair of account byte_array = base58.b58decode(pwd) keypair = list(map(lambda b: int(str(b)), byte_array))[:] initializer_account = Keypair(keypair[0:32]) logger.info("My public key is: %s", initializer_account.public_key) txn = Transaction(recent_blockhash=client.get_recent_blockhash()[ 'result']['value']['blockhash'], fee_payer=initializer_account.public_key) # First instruction: # I have 0.785886118 LPs left, let's start with only 0.001 just in case. # => We have 9 decimals for that one, so the amount will be 0.001 * 1e9 = 1000000 # We convert 1000000 into hex: 0xF4240 -> 0x 0F 42 40 # Then we invert the bytes: 40 42 0F # So we build the instruction data: # ref: "17f74c1e96ead91e00863ba101000000" idata1 = "17f74c1e96ead91e40420f0000000000" txn.add( TransactionInstruction( keys=[ AccountMeta(pubkey=PublicKey(initializer_account.public_key), # 1 is_signer=True, is_writable=True), AccountMeta( pubkey=PublicKey('5mLQTfsdDhXF7s7KYXYcKNSBWxpFVScvLUsi2F6fATMD'), # 2 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('2Mpg4g8dVseygxyRiTg9iXPWuXHWJU9xdcKZdrbYexGx'), # 3 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN'), # 4 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte'), # 5 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('97PmYbGpSHSrKrUkQX793mjpA2EA9rrQKkHsQuvenU44'), # 6 is_signer=False, is_writable=False), # Note: not writable. AccountMeta( pubkey=PublicKey('EuQRwGreQZ56tfHrB1dvQfkm8gYkKp6HPbTK1Z6fGZhy'), # 7 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('54Ex5fMemkZVuCDyAB1pxBARnUPGwAyaoJZWDdoX3LPR'), # 8 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('2xs3pAHoSwrkQbcYaEwhqtgDPpNcK9akQdUqZ8r763nS'), # 9 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), # 10 is_signer=False, is_writable=False), AccountMeta( pubkey=PublicKey('QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB'), # 11 is_signer=False, is_writable=False), ], program_id=PublicKey(program), data=bytes.fromhex(idata1) ) ) # Second instruction: # we build the instruction data: # ref: "39eabc535ce92cc700863ba101000000" idata2 = "39eabc535ce92cc740420f0000000000" txn.add( TransactionInstruction( keys=[ AccountMeta(pubkey=PublicKey(initializer_account.public_key), # 1 is_signer=True, is_writable=True), AccountMeta( pubkey=PublicKey('HRavzHF4bymJ59BGgEVRvNSx74zq7ceD2Dwxb9fMvZ87'), # 2 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN'), # 3 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte'), # 4 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk'), # 5 is_signer=False, is_writable=False), # Note: not writable. AccountMeta( pubkey=PublicKey('8uEjJsJ5cCbz7m4K9jZEmQB6cL3SM3V2suc6fazkgWan'), # 6 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('7DcyStUnaDVySSMz9aFucgnKEQxtFbELzRY1gk5vTMhw'), # 7 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('CSpYqVQx1cZRJdApjuMSyNqB3mUrC6ify5u7NYzVEK8S'), # 8 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), # 9 is_signer=False, is_writable=False), AccountMeta( pubkey=PublicKey('QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB'), # 10 is_signer=False, is_writable=False), ], program_id=PublicKey(program), data=bytes.fromhex(idata2) ) ) # Third instruction: # we build the instruction data: # ref: "b422252e9c00d3ee" idata3 = "b422252e9c00d3ee" txn.add( TransactionInstruction( keys=[ AccountMeta(pubkey=PublicKey(initializer_account.public_key), # 1 is_signer=True, is_writable=True), AccountMeta( pubkey=PublicKey('D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN'), # 2 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte'), # 3 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('HRavzHF4bymJ59BGgEVRvNSx74zq7ceD2Dwxb9fMvZ87'), # 4 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('7TuJswHsetUW11fBLQSHe4msTRRGB1KDGL9XBa5v7ur6'), # 5 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('HiEYoocRJtrRREuDLyXVBYm3mNAEscGyw1PGJ6jmn6JE'), # 6 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), # 7 is_signer=False, is_writable=False), ], program_id=PublicKey(program), data=bytes.fromhex(idata3) ) ) # sign and send logger.info("Sending transaction...") txn.sign(initializer_account) rpc_response = client.send_transaction( txn, initializer_account, opts=TxOpts(skip_preflight=True, skip_confirmation=False) ) logger.info("Got RPC response: %s", rpc_response) def process_command(self, cmd): """Check if this component can process the given command""" if cmd == 'withdraw': logger.info("Trying to withdraw funds...") self.withdraw_funds() return True return False if __name__ == "__main__": # Create the context: context = NVPContext() # Add our component: comp = context.register_component("SunnyAg", SunnyAg(context)) context.define_subparsers("main", { 'withdraw': None, }) comp.run()
$ nvp sunnyag withdraw 2022/05/19 20:43:05 [__main__] INFO: Trying to withdraw funds... Chrome -> Phantom -> Settings -> Export private Key -> paste it here: 2022/05/19 20:43:40 [__main__] INFO: My public key is: 7X2mFEFeddSsK2WMCP3rqv7fzLPs6MLCSG1GWEmGzfDn 2022/05/19 20:43:41 [__main__] INFO: Sending transaction... 2022/05/19 20:43:41 [solanaweb3.rpc.httprpc.HTTPClient] INFO: Transaction sent to https://api.mainnet-beta.solana.com. Signature W64TEFZrvcnBcpTeoxg6Qvqb6ixq7PoNtdkP5b5qp qAUCpMcp4R9PwzqtYMcSjT7Nc4LRYtyrVShgbopokkMuYA: Traceback (most recent call last): File "D:\Projects\NervHome\nvh\crypto\sunny_ag.py", line 245, in <module> comp.run() File "D:\Projects\NervProj\nvp\nvp_component.py", line 69, in run res = self.process_command(cmd) File "D:\Projects\NervHome\nvh\crypto\sunny_ag.py", line 228, in process_command self.withdraw_funds() File "D:\Projects\NervHome\nvh\crypto\sunny_ag.py", line 215, in withdraw_funds rpc_response = client.send_transaction( File "D:\Projects\NervProj\.pyenvs\solana_env\lib\site-packages\solana\rpc\api.py", line 1281, in send_transaction txn_resp = self.send_raw_transaction(txn.serialize(), opts=opts) File "D:\Projects\NervProj\.pyenvs\solana_env\lib\site-packages\solana\rpc\api.py", line 1235, in send_raw_transaction return self.__post_send_with_confirm(*post_send_args) File "D:\Projects\NervProj\.pyenvs\solana_env\lib\site-packages\solana\rpc\api.py", line 1347, in __post_send_with_confirm self.confirm_transaction(resp["result"], conf_comm) File "D:\Projects\NervProj\.pyenvs\solana_env\lib\site-packages\solana\rpc\api.py", line 1378, in confirm_transaction raise UnconfirmedTxError(f"Unable to confirm transaction {tx_sig}") solana.rpc.core.UnconfirmedTxError: Unable to confirm transaction W64TEFZrvcnBcpTeoxg6Qvqb6ixq7PoNtdkP5b5qpqAUCpMcp4R9PwzqtYMcSjT7Nc4LRYtyrVShgbopokkMuYA Traceback (most recent call last): File "D:\Projects\NervProj\cli.py", line 5, in <module> ctx.run() File "D:\Projects\NervProj\nvp\nvp_context.py", line 403, in run if comp.process_command(cmd): File "D:\Projects\NervProj\nvp\components\runner.py", line 42, in process_command self.run_script(sname, proj) File "D:\Projects\NervProj\nvp\components\runner.py", line 155, in run_script self.execute(cmd, cwd=cwd, env=env) File "D:\Projects\NervProj\nvp\nvp_object.py", line 422, in execute subprocess.check_call(cmd, stdout=stdout, stderr=stderr, cwd=cwd, env=env) File "D:\Projects\NervProj\tools\windows\python-3.10.1\lib\subprocess.py", line 369, in check_call raise CalledProcessError(retcode, cmd) subprocess.CalledProcessError: Command '['D:\\Projects\\NervProj\\.pyenvs\\solana_env\\python.exe', 'D:\\Projects\\NervHome/nvh/crypto/sunny_ag.py', 'withdraw']' returned non-zero exit status 1.
"""Module for SunnyAg class definition""" import logging import os import time # from getpass import getpass from solana.transaction import AccountMeta, Transaction, TransactionInstruction from solana.rpc.types import TxOpts from solana.rpc.api import Client from solana.publickey import PublicKey from solana.keypair import Keypair from solana.rpc.core import UnconfirmedTxError # from solana.account import Account # from solana.rpc.commitment import Recent, Root # from solana.system_program import create_account, CreateAccountParams # from spl.token.instructions import set_authority, SetAuthorityParams, AuthorityType import base58 from nvp.nvp_context import NVPContext from nvp.nvp_component import NVPComponent logger = logging.getLogger(__name__) class SunnyAg(NVPComponent): """SunnyAg component class""" def __init__(self, ctx: NVPContext): """class constructor""" NVPComponent.__init__(self, ctx) def withdraw_funds(self, amount): """method used to withdraw our funds from a given pool.""" url = 'https://api.mainnet-beta.solana.com' client = Client(url) # pwd = getpass('Chrome -> Phantom -> Settings -> Export private Key -> paste it here: ') priv_key = os.getenv('PHANTOM_KEY') assert priv_key is not None, "Phantom private key not set." # cf. https://solscan.io/tx/MUHWqsXtxQnmFzKE5UNSvfCDiikoihXW64Z9bVcYBsi7rBsML2HNXsaXPtxon7tRvSL2RLFgWfXj2DYVrvhoyTt # as reference for the setup below: # id of the program: program = 'SPQR4kT3q2oUKEJes2L6NNSBCiPW9SfuhkuqC9bp6Sx' # get int based keypair of account byte_array = base58.b58decode(priv_key) keypair = list(map(lambda b: int(str(b)), byte_array))[:] initializer_account = Keypair(keypair[0:32]) logger.info("My public key is: %s", initializer_account.public_key) txn = Transaction(recent_blockhash=client.get_recent_blockhash()[ 'result']['value']['blockhash'], fee_payer=initializer_account.public_key) # First instruction: # I have 0.785886118 LPs left, let's start with only 0.001 just in case. # => We have 9 decimals for that one, so the amount will be 0.001 * 1e9 = 1000000 # We convert 1000000 into hex: 0xF4240 -> 0x 0F 42 40 # Then we invert the bytes: 40 42 0F # So we build the instruction data: # ref: "17f74c1e96ead91e00863ba101000000" # idata1 = "17f74c1e96ead91e40420f0000000000" # Update: # Below we compute the amount representation from the integer value provided as # argument (Note: integer value because we assume the decimal multiplier is already # taken into account) # convert the amount to key (we remove the "0x" prefix) val = hex(amount)[2:] # add a prefix 0 if the number of char is odd: if len(val) % 2 != 0: val = f"0{val}" # Now we split in bytes: bval = [val[i:i+2] for i in range(0, len(val), 2)] bval.reverse() amount_rep = "".join(bval) # the value we send should take 16 characters, so we add the required 0 at the end: left = 16 - len(amount_rep) amount_rep += "0"*left logger.info("Using amount representation: %s", amount_rep) idata1 = f"17f74c1e96ead91e{amount_rep}" txn.add( TransactionInstruction( keys=[ AccountMeta(pubkey=PublicKey(initializer_account.public_key), # 1 is_signer=True, is_writable=True), AccountMeta( pubkey=PublicKey('5mLQTfsdDhXF7s7KYXYcKNSBWxpFVScvLUsi2F6fATMD'), # 2 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('2Mpg4g8dVseygxyRiTg9iXPWuXHWJU9xdcKZdrbYexGx'), # 3 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN'), # 4 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte'), # 5 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('97PmYbGpSHSrKrUkQX793mjpA2EA9rrQKkHsQuvenU44'), # 6 is_signer=False, is_writable=False), # Note: not writable. AccountMeta( pubkey=PublicKey('EuQRwGreQZ56tfHrB1dvQfkm8gYkKp6HPbTK1Z6fGZhy'), # 7 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('54Ex5fMemkZVuCDyAB1pxBARnUPGwAyaoJZWDdoX3LPR'), # 8 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('2xs3pAHoSwrkQbcYaEwhqtgDPpNcK9akQdUqZ8r763nS'), # 9 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), # 10 is_signer=False, is_writable=False), AccountMeta( pubkey=PublicKey('QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB'), # 11 is_signer=False, is_writable=False), ], program_id=PublicKey(program), data=bytes.fromhex(idata1) ) ) # Second instruction: # we build the instruction data: # ref: "39eabc535ce92cc700863ba101000000" # idata2 = "39eabc535ce92cc740420f0000000000" idata2 = f"39eabc535ce92cc7{amount_rep}" txn.add( TransactionInstruction( keys=[ AccountMeta(pubkey=PublicKey(initializer_account.public_key), # 1 is_signer=True, is_writable=True), AccountMeta( pubkey=PublicKey('HRavzHF4bymJ59BGgEVRvNSx74zq7ceD2Dwxb9fMvZ87'), # 2 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN'), # 3 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte'), # 4 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk'), # 5 is_signer=False, is_writable=False), # Note: not writable. AccountMeta( pubkey=PublicKey('8uEjJsJ5cCbz7m4K9jZEmQB6cL3SM3V2suc6fazkgWan'), # 6 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('7DcyStUnaDVySSMz9aFucgnKEQxtFbELzRY1gk5vTMhw'), # 7 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('CSpYqVQx1cZRJdApjuMSyNqB3mUrC6ify5u7NYzVEK8S'), # 8 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), # 9 is_signer=False, is_writable=False), AccountMeta( pubkey=PublicKey('QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB'), # 10 is_signer=False, is_writable=False), ], program_id=PublicKey(program), data=bytes.fromhex(idata2) ) ) # Third instruction: # we build the instruction data: # ref: "b422252e9c00d3ee" idata3 = "b422252e9c00d3ee" txn.add( TransactionInstruction( keys=[ AccountMeta(pubkey=PublicKey(initializer_account.public_key), # 1 is_signer=True, is_writable=True), AccountMeta( pubkey=PublicKey('D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN'), # 2 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte'), # 3 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('HRavzHF4bymJ59BGgEVRvNSx74zq7ceD2Dwxb9fMvZ87'), # 4 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('7TuJswHsetUW11fBLQSHe4msTRRGB1KDGL9XBa5v7ur6'), # 5 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('HiEYoocRJtrRREuDLyXVBYm3mNAEscGyw1PGJ6jmn6JE'), # 6 is_signer=False, is_writable=True), AccountMeta( pubkey=PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), # 7 is_signer=False, is_writable=False), ], program_id=PublicKey(program), data=bytes.fromhex(idata3) ) ) # sign and send logger.info("Sending transaction...") txn.sign(initializer_account) rpc_response = client.send_transaction( txn, initializer_account, opts=TxOpts(skip_preflight=True, skip_confirmation=True) ) # the transaction hash should be in the 'result' key: logger.info("Got RPC response: %s", rpc_response) txhash = rpc_response['result'] count = 0 # wait a short moment: time.sleep(5.0) max_count = 10 while count < max_count: # cf. https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses res = client.get_signature_statuses([txhash]) if res[0] is None: logger.info("Transaction %s is unknown.", txhash) count += 1 else: # we have an object: obj = res[0] status = obj.get('confirmationStatus', None) if status == "confirmed": logger.info("Transaction %s is confirmed!", txhash) break else: logger.info("Transaction status: %s", self.pretty_print(obj)) count += 1 logger.info("Waiting for confirmation...") # Wait 1 minute: time.sleep(60) if count == max_count: logger.warning("Could not confirm transaction %s, please check manually.", txhash) def process_command(self, cmd): """Check if this component can process the given command""" if cmd == 'withdraw': logger.info("Trying to withdraw funds...") amount = self.get_param("amount") self.withdraw_funds(amount) return True return False if __name__ == "__main__": # Create the context: context = NVPContext() # Add our component: comp = context.register_component("SunnyAg", SunnyAg(context)) context.define_subparsers("main", { 'withdraw': None, }) psr = context.get_parser('main.withdraw') psr.add_argument("amount", type=int, help="Amount to withdraw") comp.run()
$ nvp sunnyag withdraw 784886118 2022/05/19 22:03:33 [__main__] INFO: Trying to withdraw funds... 2022/05/19 22:03:33 [__main__] INFO: My public key is: 7X2mFEFeddSsK2WMCP3rqv7fzLPs6MLCSG1GWEmGzfDn 2022/05/19 22:03:34 [__main__] INFO: Using amount representation: 6669c82e00000000 2022/05/19 22:03:34 [__main__] INFO: Sending transaction... 2022/05/19 22:03:35 [__main__] INFO: Got RPC response: {'jsonrpc': '2.0', 'result': '3YxVCFBHAYoNEsmg962spiZTNLthzLLvz92g9CRCW2H6fhtSauBv8EncTQvYfQdrLf6Y4HsyJbWPpa9X1fZnwu9p', 'id': 3} 2022/05/19 22:03:35 [__main__] INFO: Waiting for confirmation... Traceback (most recent call last): File "D:\Projects\NervHome\nvh\crypto\sunny_ag.py", line 278, in <module> comp.run() File "D:\Projects\NervProj\nvp\nvp_component.py", line 69, in run res = self.process_command(cmd) File "D:\Projects\NervHome\nvh\crypto\sunny_ag.py", line 257, in process_command self.withdraw_funds(amount) File "D:\Projects\NervHome\nvh\crypto\sunny_ag.py", line 234, in withdraw_funds if res[0] is None: KeyError: 0 Traceback (most recent call last): File "D:\Projects\NervProj\cli.py", line 5, in <module> ctx.run() File "D:\Projects\NervProj\nvp\nvp_context.py", line 403, in run if comp.process_command(cmd): File "D:\Projects\NervProj\nvp\components\runner.py", line 42, in process_command self.run_script(sname, proj) File "D:\Projects\NervProj\nvp\components\runner.py", line 155, in run_script self.execute(cmd, cwd=cwd, env=env) File "D:\Projects\NervProj\nvp\nvp_object.py", line 422, in execute subprocess.check_call(cmd, stdout=stdout, stderr=stderr, cwd=cwd, env=env) File "D:\Projects\NervProj\tools\windows\python-3.10.1\lib\subprocess.py", line 369, in check_call raise CalledProcessError(retcode, cmd) subprocess.CalledProcessError: Command '['D:\\Projects\\NervProj\\.pyenvs\\solana_env\\python.exe', 'D:\\Projects\\NervHome/nvh/crypto/sunny_ag.py', 'withdraw', '7848861 18']' returned non-zero exit status 1.
"""Module for SunnyAg class definition""" import logging import os import time # from getpass import getpass from solana.transaction import AccountMeta, Transaction, TransactionInstruction from solana.rpc.types import TxOpts from solana.rpc.api import Client from solana.publickey import PublicKey from solana.keypair import Keypair # from solana.account import Account # from solana.rpc.commitment import Recent, Root # from solana.system_program import create_account, CreateAccountParams # from spl.token.instructions import set_authority, SetAuthorityParams, AuthorityType import base58 from nvp.nvp_context import NVPContext from nvp.nvp_component import NVPComponent logger = logging.getLogger(__name__) # Listing of all accounts used as inputs to withdraw from a given sunnyag pool: # **Note**: an exclamation mark at the beginning of the account key means that # the account will be used in readonly mode (ie. not writable) when building the # transaction instruction below. ALL_ACCOUNTS = { "pSOL_prtSOL": [ [ "5mLQTfsdDhXF7s7KYXYcKNSBWxpFVScvLUsi2F6fATMD", "2Mpg4g8dVseygxyRiTg9iXPWuXHWJU9xdcKZdrbYexGx", "D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN", "4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte", "!97PmYbGpSHSrKrUkQX793mjpA2EA9rrQKkHsQuvenU44", "EuQRwGreQZ56tfHrB1dvQfkm8gYkKp6HPbTK1Z6fGZhy", "54Ex5fMemkZVuCDyAB1pxBARnUPGwAyaoJZWDdoX3LPR", "2xs3pAHoSwrkQbcYaEwhqtgDPpNcK9akQdUqZ8r763nS", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "!QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", ], [ "HRavzHF4bymJ59BGgEVRvNSx74zq7ceD2Dwxb9fMvZ87", "D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN", "4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte", "!rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk", "8uEjJsJ5cCbz7m4K9jZEmQB6cL3SM3V2suc6fazkgWan", "7DcyStUnaDVySSMz9aFucgnKEQxtFbELzRY1gk5vTMhw", "CSpYqVQx1cZRJdApjuMSyNqB3mUrC6ify5u7NYzVEK8S", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "!QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", ], [ "D7FXWcnRGRrxypYZvujosegypQ9MsZ6cZMoh8qAJVmqN", "4uKud9gWbseDAyENU3dnei21rFwRbXofgSsvScFNUbte", "HRavzHF4bymJ59BGgEVRvNSx74zq7ceD2Dwxb9fMvZ87", "7TuJswHsetUW11fBLQSHe4msTRRGB1KDGL9XBa5v7ur6", "HiEYoocRJtrRREuDLyXVBYm3mNAEscGyw1PGJ6jmn6JE", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", ] ], "xFTT_wFTT": [ [ "F7tt5sohvc1D4dvn2cmJkD2mR6cwtujWG2naKX8x7L7o", "FizkdyUeTRs8XYhA7v9d1XjQ4U25YUx7igJn6xQoXu76", "Ex33MaUfcdDUKfsdxsLWCqy4zEheQcLs8X45gBaiKJgF", "GYrfqfN6n1csWgzQTvChYNBGduddjfCQnMT6w6eKEseQ", "!97PmYbGpSHSrKrUkQX793mjpA2EA9rrQKkHsQuvenU44", "EuLpc9s1caxWetHBLRxnetQUKkAygngRb1PgYA8qwK1S", "5Y2RrrgNaD9tJYyGeqQ2zp9d1BJP6qEH46U2JyrSteQ4", "4U86Cm3twisfWYwFuEPRLBWJdQGkSu5V67Jg9makygG7", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "!QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", ], [ "3UnZAhERbCB8NRhxnVEpXh8mJGha99RJv79tRZGw4NSo", "Ex33MaUfcdDUKfsdxsLWCqy4zEheQcLs8X45gBaiKJgF", "GYrfqfN6n1csWgzQTvChYNBGduddjfCQnMT6w6eKEseQ", "!rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk", "FdHVdkarMfbpPhiAhHtKK6PgH8ibN8SnGSqWhQkTyD2c", "EF39s4uGKyPgqgmJVrv8UvmpKGSoKdVSkpT4GDPHo59i", "99SQokGqorXikQ52cofLRX9kFx475Fg3X57EvVusqNEZ", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "!QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", ], [ "Ex33MaUfcdDUKfsdxsLWCqy4zEheQcLs8X45gBaiKJgF", "GYrfqfN6n1csWgzQTvChYNBGduddjfCQnMT6w6eKEseQ", "3UnZAhERbCB8NRhxnVEpXh8mJGha99RJv79tRZGw4NSo", "2dzG4gXCkAaCpSm7swCrADE6qfVbt2wquyby23NGa35V", "AZcbkuFqeTMacxd1GBcMypqqX19YfWL1i9dMxneqgu2W", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", ] ] } class SunnyAg(NVPComponent): """SunnyAg component class""" def __init__(self, ctx: NVPContext): """class constructor""" NVPComponent.__init__(self, ctx) def withdraw_funds(self, amount, pool_name): """method used to withdraw our funds from a given pool.""" url = 'https://api.mainnet-beta.solana.com' client = Client(url) # pwd = getpass('Chrome -> Phantom -> Settings -> Export private Key -> paste it here: ') priv_key = os.getenv('PHANTOM_KEY') assert priv_key is not None, "Phantom private key not set." # cf. https://solscan.io/tx/MUHWqsXtxQnmFzKE5UNSvfCDiikoihXW64Z9bVcYBsi7rBsML2HNXsaXPtxon7tRvSL2RLFgWfXj2DYVrvhoyTt # as reference for the setup below: # id of the program: program = 'SPQR4kT3q2oUKEJes2L6NNSBCiPW9SfuhkuqC9bp6Sx' # get int based keypair of account byte_array = base58.b58decode(priv_key) keypair = list(map(lambda b: int(str(b)), byte_array))[:] initializer_account = Keypair(keypair[0:32]) logger.info("My public key is: %s", initializer_account.public_key) txn = Transaction(recent_blockhash=client.get_recent_blockhash()[ 'result']['value']['blockhash'], fee_payer=initializer_account.public_key) # First instruction: # I have 0.785886118 LPs left, let's start with only 0.001 just in case. # => We have 9 decimals for that one, so the amount will be 0.001 * 1e9 = 1000000 # We convert 1000000 into hex: 0xF4240 -> 0x 0F 42 40 # Then we invert the bytes: 40 42 0F # So we build the instruction data: # ref: "17f74c1e96ead91e00863ba101000000" # idata1 = "17f74c1e96ead91e40420f0000000000" # Update: # Below we compute the amount representation from the integer value provided as # argument (Note: integer value because we assume the decimal multiplier is already # taken into account) # convert the amount to key (we remove the "0x" prefix) val = hex(amount)[2:] # add a prefix 0 if the number of char is odd: if len(val) % 2 != 0: val = f"0{val}" # Now we split in bytes: bval = [val[i:i+2] for i in range(0, len(val), 2)] bval.reverse() amount_rep = "".join(bval) # the value we send should take 16 characters, so we add the required 0 at the end: left = 16 - len(amount_rep) amount_rep += "0"*left logger.info("Using amount representation: %s", amount_rep) logger.info("Pool name: %s", pool_name) assert pool_name in ALL_ACCOUNTS, "Invalid pool name" accounts = ALL_ACCOUNTS[pool_name] def build_inputs(addrs): """Build the inputs from a list of addresses""" inputs = [AccountMeta(pubkey=PublicKey(initializer_account.public_key), is_signer=True, is_writable=True)] for addr in addrs: write = True if addr[0] == "!": write = False addr = addr[1:] # logger.info("Making %s read only", addr) inputs.append(AccountMeta(pubkey=PublicKey(addr), is_signer=False, is_writable=write)) return inputs txn.add( TransactionInstruction( keys=build_inputs(accounts[0]), program_id=PublicKey(program), data=bytes.fromhex(f"17f74c1e96ead91e{amount_rep}") ) ) # Second instruction: txn.add( TransactionInstruction( keys=build_inputs(accounts[1]), program_id=PublicKey(program), data=bytes.fromhex(f"39eabc535ce92cc7{amount_rep}") ) ) # Third instruction: txn.add( TransactionInstruction( keys=build_inputs(accounts[2]), program_id=PublicKey(program), data=bytes.fromhex("b422252e9c00d3ee") ) ) # sign and send logger.info("Sending transaction...") txn.sign(initializer_account) rpc_response = client.send_transaction( txn, initializer_account, opts=TxOpts(skip_preflight=False, skip_confirmation=True) ) logger.info("Got RPC response: %s", rpc_response) txhash = rpc_response['result'] count = 0 # wait a short moment: time.sleep(15.0) max_count = 10 while count < max_count: # cf. https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses res = client.get_signature_statuses([txhash]) arr = res['result']['value'] if arr[0] is None: logger.info("Transaction %s is unknown.", txhash) count += 1 else: # we have an object: obj = arr[0] status = obj.get('confirmationStatus', None) if status == "finalized": logger.info("Transaction %s is finalized!", txhash) break else: logger.info("Transaction status: %s", self.pretty_print(obj)) count += 1 logger.info("Waiting for confirmation...") # Wait 30 seconds: time.sleep(30) if count == max_count: logger.warning("Could not confirm transaction %s, please check manually.", txhash) def process_command(self, cmd): """Check if this component can process the given command""" if cmd == 'withdraw': logger.info("Trying to withdraw funds...") amount = self.get_param("amount") pool_name = self.get_param("pool_name") self.withdraw_funds(amount, pool_name) return True return False if __name__ == "__main__": # Create the context: context = NVPContext() # Add our component: comp = context.register_component("SunnyAg", SunnyAg(context)) context.define_subparsers("main", { 'withdraw': None, }) psr = context.get_parser('main.withdraw') psr.add_argument("amount", type=int, help="Amount to withdraw") psr.add_argument("-p", "--pool", dest="pool_name", type=str, help="Pool name: can be 'pSOL_prtSOL' or 'xFTT_wFTT' for now") comp.run()
$ nvp sunnyag withdraw 1000000 -p xFTT_wFTT 2022/05/20 07:26:07 [__main__] INFO: Trying to withdraw funds... 2022/05/20 07:26:07 [__main__] INFO: My public key is: 7X2mFEFeddSsK2WMCP3rqv7fzLPs6MLCSG1GWEmGzfDn 2022/05/20 07:26:07 [__main__] INFO: Using amount representation: 40420f0000000000 2022/05/20 07:26:07 [__main__] INFO: Pool name: xFTT_wFTT 2022/05/20 07:26:07 [__main__] INFO: Sending transaction... 2022/05/20 07:26:08 [__main__] INFO: Got RPC response: {'jsonrpc': '2.0', 'result': 'eAbeV3TLihP4zzbBeTCen6pkUUz7Dqd8BDk7NswaGjQPLyDkrLVFb3n4teyV5jrKK7d4bmAN5KBkgFfhxwwVC23', 'id': 3} 2022/05/20 07:26:24 [__main__] INFO: Transaction status: { 'confirmationStatus': 'confirmed', 'confirmations': 10, 'err': None, 'slot': 134493920, 'status': {'Ok': None}} 2022/05/20 07:26:24 [__main__] INFO: Waiting for confirmation... 2022/05/20 07:26:54 [__main__] INFO: Transaction eAbeV3TLihP4zzbBeTCen6pkUUz7Dqd8BDk7NswaGjQPLyDkrLVFb3n4teyV5jrKK7d4bmAN5KBkgFfhxwwVC23 is finalized!
"xETH_whETH": [ [ "LDdq4KhNRuGUjno4waam4HTkyftYpeRjC6Rbjtiao8b", "6FUf8Svq5Go5Gj4mF4PUq6Y2m8ATYM8vC3pHKZ9oLVC", "BYaEPQ4aWSMkBtuzYycowy4c7C9jCnzALcZtD1fsRsj4", "gZWtNxBX8XgPoYXbYp2UUNpJeuEyzoTxKUrotYZ4uiY", "!97PmYbGpSHSrKrUkQX793mjpA2EA9rrQKkHsQuvenU44", "G1ZpzaBpqNt7hpbRnc6vbiA6fvt2wAoESpnoEM2uFKyk", "EQJBn6WZ9AQfZMhcn9jBWJAS9WpzqjGtTJcMSqGtwkvc", "4pdW8H4DpZ9zBtyE95fUeFvqCKACMkiA8obJeWHr39J7", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "!QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", ], [ "3ibyFGq9oZsAcM6c3CqRX6SoPCukU7bCcAFPq92XmLyT", "BYaEPQ4aWSMkBtuzYycowy4c7C9jCnzALcZtD1fsRsj4", "gZWtNxBX8XgPoYXbYp2UUNpJeuEyzoTxKUrotYZ4uiY", "!rXhAofQCT7NN9TUqigyEAUzV1uLL4boeD8CRkNBSkYk", "CipQjauQwCa2pBbavhtR67mKUHbd8ymrQsDwJHTxjPFB", "3vfAj1i6xWn3RzmyBcqMap7D44zs7Bbpqet4yuDoUhmn", "DbYKjt7QLcbrxQ6JEVfsVPBQfTsFDgbSLAbW8AWSPWmp", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "!QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB", ], [ "BYaEPQ4aWSMkBtuzYycowy4c7C9jCnzALcZtD1fsRsj4", "gZWtNxBX8XgPoYXbYp2UUNpJeuEyzoTxKUrotYZ4uiY", "3ibyFGq9oZsAcM6c3CqRX6SoPCukU7bCcAFPq92XmLyT", "BDLQPQcTf9cy1S7ge4vbwmwNPjd3czaeFcbFcM8caLMH", "CzxdRhX3gEFZqABWksonUGCURCtNBntAmQAaWiYgVLWE", "!TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", ] ]
$ nvp sunnyag withdraw 1000000 -p xETH_whETH 2022/05/20 07:39:33 [__main__] INFO: Trying to withdraw funds... 2022/05/20 07:39:33 [__main__] INFO: My public key is: 7X2mFEFeddSsK2WMCP3rqv7fzLPs6MLCSG1GWEmGzfDn 2022/05/20 07:39:34 [__main__] INFO: Using amount representation: 40420f0000000000 2022/05/20 07:39:34 [__main__] INFO: Pool name: xETH_whETH 2022/05/20 07:39:34 [__main__] INFO: Sending transaction... 2022/05/20 07:39:34 [__main__] INFO: Got RPC response: {'jsonrpc': '2.0', 'result': '5rh42h8onFJLMVzhimtFd5hFDALyCF2JQSqzVMw57DtCjafno8vQjgumf7KMGJzLM2LGbnvdy3FRAJeawBFeQH8J', 'id': 3} 2022/05/20 07:39:50 [__main__] INFO: Transaction status: { 'confirmationStatus': 'confirmed', 'confirmations': 0, 'err': None, 'slot': 134495101, 'status': {'Ok': None}} 2022/05/20 07:39:50 [__main__] INFO: Waiting for confirmation... 2022/05/20 07:40:20 [__main__] INFO: Transaction status: { 'confirmationStatus': 'confirmed', 'confirmations': 30, 'err': None, 'slot': 134495101, 'status': {'Ok': None}} 2022/05/20 07:40:20 [__main__] INFO: Waiting for confirmation... 2022/05/20 07:40:50 [__main__] INFO: Transaction 5rh42h8onFJLMVzhimtFd5hFDALyCF2JQSqzVMw57DtCjafno8vQjgumf7KMGJzLM2LGbnvdy3FRAJeawBFeQH8J is finalized!