solana-py icon indicating copy to clipboard operation
solana-py copied to clipboard

Transaction failed to sanitize accounts offsets correctly on SPL transfer

Open neochine opened this issue 4 months ago • 5 comments

This is send_solana_to_single(). It works with solder + raw

    def send_solana_to_single(self, from_address, to_address, sol_amount, signer_keypairs=[]):
        if not from_address:
            from_address = self.keypair.pubkey()
        elif from_address and isinstance(from_address, str):
            from_address = Pubkey.from_string(from_address)
        if isinstance(to_address, str):
            to_address = Pubkey.from_string(to_address)
        if not signer_keypairs:
            signer_keypairs = [self.keypair]

        transaction = Transaction()
        sol_transfer_params = SolTransferParams(from_pubkey=from_address, to_pubkey=to_address, lamports=sol_to_lamport(sol_amount))
        sol_transfer_instruction = SolTransfer(sol_transfer_params)
        transaction.add(sol_transfer_instruction)

        transaction = transaction.to_solders()
        blockhash = self.client.retryable_get_latest_blockhash_value()
        transaction.sign(signer_keypairs, blockhash.blockhash)
        signature = self.client.retryable_send_raw_transaction(transaction)
        return signature

I implement similar for spl

def send_spl_to_single(self, from_address, to_address, spl_address, spl_amount, signer_keypairs=[]):
        if not from_address:
            from_address = self.keypair.pubkey()
        elif from_address and isinstance(from_address, str):
            from_address = Pubkey.from_string(from_address)
        if isinstance(to_address, str):
            to_address = Pubkey.from_string(to_address)
        if isinstance(spl_address, str):
            spl_address = Pubkey.from_string(spl_address)
        if not signer_keypairs:
            signer_keypairs = [self.keypair]
        for n in range(3):
            account_info = self.client.retryable_get_account_info_json_parsed(spl_address)
            parsed_account_info = AccountInfoParser(account_info, spl_address)
            if parsed_account_info.valid_account_info:
                break
            elif n == 3:
                raise ValueError(f"Cant find account info for spl token {spl_address}")
        if parsed_account_info.account_type == 'TOKEN_ACCOUNT' and parsed_account_info.owner == Constants.TOKEN_PROGRAM_ID:
            from_address_ata = get_associated_token_address(from_address, spl_address)
            to_address_ata = get_associated_token_address(to_address, spl_address)
            program_id = Constants.TOKEN_PROGRAM_ID
        elif parsed_account_info.account_type == 'TOKEN_ACCOUNT' and parsed_account_info.owner == Constants.TOKEN_2022_PROGRAM_ID:
            from_address_ata = None
            to_address_ata = None
            program_id = Constants.TOKEN_2022_PROGRAM_ID
        else:
            print(f"SPL address is wrong type {spl_address} {parsed_account_info.account_type}")
            raise ValueError

        # Check if to_address ATA exists, if not create them
        all_spl = self.get_all_spl(to_address) #token_client.get_token_accounts_by_owner does same thing
        if not (str(spl_address) in all_spl):
            print(f"Creating associated token account for {to_address}")
            token_manager = TokenManager(self.client)
            token_manager.create_associated_token_account(spl_address, program_id, signer_keypairs, to_address)

        if isinstance(program_id, str):
            program_id = Pubkey.from_string(program_id)
        transaction = Transaction()
        spl_transfer_params = SplTransferCheckedParams(
            program_id=program_id,
            source=from_address_ata,
            mint=spl_address,
            dest=to_address_ata,
            owner=signer_keypairs[0].pubkey(),
            amount=spl_amount,
            decimals=all_spl[str(spl_address)]['decimals'] #checked uses decimals
        )
        spl_transfer_instruction = SplTransferChecked(spl_transfer_params)
transaction.add(spl_transfer_instruction)

        signature = self.client.retryable_send_transaction(transaction, signer_keypairs[0])
        #transaction = transaction.to_solders()
        #blockhash = self.client.retryable_get_latest_blockhash_value()
        #transaction.sign(signer_keypairs, blockhash.blockhash)
        #signature = self.client.retryable_send_raw_transaction(transaction)
        return signature

It works when I use retryable_send_transaction. I am doing it in devnet

>>> sent
SendTransactionResp(
    Signature(
        445CE2jpJEMEfvvKygjyvf9h79CSnQ8N6KyP756jeU73Ve6JZeH9uN37VSKAUCJCbqss6yB15KdQp34TSzgLCPgH,
    ),
)

but when i use retryable_send_raw_transaction

        #signature = self.client.retryable_send_transaction(transaction, signer_keypairs[0])
        transaction = transaction.to_solders()
        blockhash = self.client.retryable_get_latest_blockhash_value()
        transaction.sign(signer_keypairs, blockhash.blockhash)
        signature = self.client.retryable_send_raw_transaction(transaction)
        return signature

I get

results = solana_client.send_raw_transaction(signed_transaction, opts=tx_opts)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/usr/local/lib/python3.11/dist-packages/solana/rpc/api.py", line 996, in send_raw_transaction\n    return self._post_send(resp)\n           ^^^^^^^^^^^^^^^^^^^^^\n  File "/usr/local/lib/python3.11/dist-packages/solana/rpc/core.py", line 511, in _post_send\n    raise RPCNoResultException(resp.message)\nsolana.rpc.core.RPCNoResultException: invalid transaction: Transaction failed to sanitize accounts offsets correctly

neochine avatar Oct 02 '24 15:10 neochine