Source code for agora.error
from enum import IntEnum
from typing import List, Optional
from agoraapi.common.v3 import model_pb2 as model_pb
from agoraapi.common.v4 import model_pb2 as model_pb_v4
from kin_base import transaction_envelope as te, operation
from agora import solana
from agora.solana import decompile_transfer
[docs]class Error(Exception):
"""Base error for Agora SDK errors.
"""
def __init__(self, message: Optional[str] = ''):
self.message = message
super().__init__(self.message)
[docs]class TransactionError(Error):
"""Base error for transaction submission errors.
:param tx_id: The id of the transaction, if available.
"""
def __init__(self, message: Optional[str] = '', tx_id: Optional[bytes] = None):
super().__init__(message)
self.tx_id = tx_id
[docs]class UnsupportedVersionError(Error):
"""Raised when an unsupported version of Kin is used
"""
[docs]class UnsupportedMethodError(Error):
"""Raised when a method is not supported in the current environment.
"""
[docs]class AccountExistsError(Error):
"""Raised when trying to create an account that already exists.
"""
[docs]class TransactionNotFoundError(Error):
"""Raised when no transaction data for a specified transaction could be found.
"""
[docs]class AccountNotFoundError(TransactionError):
"""Raised when an account could not be found.
"""
[docs]class InvalidSignatureError(TransactionError):
"""Raised when the submitted transaction is either missing signatures or
contains unused ones.
"""
[docs]class InsufficientBalanceError(TransactionError):
"""Raised when an account has an insufficient balance for a submitted
transaction.
"""
[docs]class BadNonceError(TransactionError):
"""Raised when a transaction contains an invalid nonce."""
[docs]class AlreadySubmittedError(TransactionError):
"""Indicates that the transaction was already submitted.
If the client is retrying a submission due to a transient failure, then this can occur if the submission in a
previous attempt was successful. Otherwise, it may indicate that the transaction is indistinguishable from a
previous transaction (i.e. same block hash, sender, dest, and amount), and the client should use a different recent
blockhash and try again.
"""
[docs]class WebhookRequestError(Error):
"""Should be raised to return an error to Agora from a webhook.
:param status_code: The status code to respond with.
:param response_body: The response body to respond with.
"""
def __init__(self, status_code: int, response_body: str = ""):
super().__init__()
self.status_code = status_code
self.response_body = response_body
[docs]class InvoiceErrorReason(IntEnum):
UNKNOWN = 0
ALREADY_PAID = 1
WRONG_DESTINATION = 2
SKU_NOT_FOUND = 3
[docs] @classmethod
def from_proto(
cls, proto: model_pb.InvoiceError.Reason
) -> 'InvoiceErrorReason':
if proto == model_pb.InvoiceError.Reason.ALREADY_PAID:
return cls.ALREADY_PAID
if proto == model_pb.InvoiceError.Reason.WRONG_DESTINATION:
return cls.WRONG_DESTINATION
if proto == model_pb.InvoiceError.Reason.SKU_NOT_FOUND:
return cls.SKU_NOT_FOUND
return cls.UNKNOWN
[docs] def to_lowercase(self):
return self.name.lower()
[docs]class OperationInvoiceError(Error):
def __init__(self, op_index: int, reason: InvoiceErrorReason):
super().__init__()
self.op_index = op_index
self.reason = reason
[docs] def to_json(self) -> dict:
return {
'operation_index': self.op_index,
'reason': self.reason.to_lowercase()
}
[docs]class AlreadyPaidError(Error):
"""Raised when an invoice has already been paid.
"""
[docs]class WrongDestinationError(Error):
"""Raised when a transaction was rejected by the app webhook for having a wrong destination.
"""
[docs]class SkuNotFoundError(Error):
"""Raised when an invoice contains a SKU that could not be found.
"""
[docs]class TransactionRejectedError(Error):
"""Raised when the submitted transaction was rejected by a configured webhook.
"""
[docs]class BlockchainVersionError(Error):
"""Raised when Agora indicates that the current blockchain version is not supported.
"""
[docs]class InvoiceError(Error):
"""Raised when there was an issue with a provided invoice.
"""
def __init__(self, errors: List[OperationInvoiceError], *args, **kwargs):
super().__init__(*args, **kwargs)
self.errors = errors
[docs]class PayerRequiredError(Error):
"""Raised when a transaction is missing a signature from its funder. This can occur if the service does not have a
subsidizer configured, or if it refuses to subsidize this specific transaction. The latter case can occur during
rate limiting situations. In this case, the client may either try at a later time, or attempt to fund the
transaction using a different account."""
[docs]class NoSubsidizerError(Error):
"""Raised when no subsidizer was provided for a transaction. This occurs if no subsidizer was made available by the
Agora service and none was provided by the method caller."""
[docs]class NoTokenAccountsError(Error):
"""Indicates that no token accounts were resolved for the requested account ID.
"""
[docs]class TransactionErrors:
"""Contains the details of a failed transaction.
:param tx_error: (optional) A :class:`Error <Error>` object. If present, the transaction failed. Otherwise, it was
successful.
:param op_errors: (optional) A list of optional :class:`Error <Error>` objects. Each error corresponds to an
operation in the submitted transaction. If present, the length of this list will match the number of operations
submitted. If the value corresponding to a specific operation is None, it does not indicate that the operation
succeeded, only that it was not the reason that the transaction failed.
"""
def __init__(self, tx_error: Optional[Error] = None, op_errors: Optional[List[Optional[Error]]] = None,
payment_errors: Optional[List[Optional[Error]]] = None):
self.tx_error = tx_error
self.op_errors = op_errors if op_errors else []
self.payment_errors = payment_errors if payment_errors else []
[docs] @staticmethod
def from_solana_tx(
tx: solana.Transaction, tx_error: model_pb_v4.TransactionError, tx_id: bytes
) -> Optional['TransactionErrors']:
err = error_from_proto(tx_error, tx_id)
if not err:
return None
errors = TransactionErrors(err)
if tx_error.instruction_index >= 0:
errors.op_errors = [None] * len(tx.message.instructions)
errors.op_errors[tx_error.instruction_index] = err
paymentIndex = tx_error.instruction_index
paymentCount = 0
for idx, instruction in enumerate(tx.message.instructions):
try:
decompile_transfer(tx.message, idx)
paymentCount += 1
except ValueError:
if idx < tx_error.instruction_index:
paymentIndex -= 1
elif idx == tx_error.instruction_index:
paymentIndex = -1
if paymentIndex > -1:
errors.payment_errors = [None] * paymentCount
errors.payment_errors[paymentIndex] = err
return errors
[docs] @staticmethod
def from_stellar_tx(env: te.TransactionEnvelope, tx_error: model_pb_v4.TransactionError, tx_id: bytes) -> Optional[
'TransactionErrors']:
err = error_from_proto(tx_error, tx_id)
if not err:
return None
errors = TransactionErrors(err)
if tx_error.instruction_index >= 0:
errors.op_errors = [None] * len(env.tx.operations)
errors.op_errors[tx_error.instruction_index] = err
paymentIndex = tx_error.instruction_index
paymentCount = 0
for idx, op in enumerate(env.tx.operations):
if isinstance(op, operation.Payment):
paymentCount += 1
elif idx < tx_error.instruction_index:
paymentIndex -= 1
elif idx == tx_error.instruction_index:
paymentIndex = -1
if paymentIndex > -1:
errors.payment_errors = [None] * paymentCount
errors.payment_errors[paymentIndex] = err
return errors
[docs]def error_from_proto(tx_error: model_pb_v4.TransactionError, tx_id: bytes) -> Optional[Error]:
if tx_error.reason == model_pb_v4.TransactionError.NONE:
return None
if tx_error.reason == model_pb_v4.TransactionError.UNAUTHORIZED:
return InvalidSignatureError(tx_id=tx_id)
if tx_error.reason == model_pb_v4.TransactionError.BAD_NONCE:
return BadNonceError(tx_id=tx_id)
if tx_error.reason == model_pb_v4.TransactionError.INSUFFICIENT_FUNDS:
return InsufficientBalanceError(tx_id=tx_id)
if tx_error.reason == model_pb_v4.TransactionError.INVALID_ACCOUNT:
return AccountNotFoundError(tx_id=tx_id)
return Error(f'unknown tx error reason: {tx_error.reason}')
[docs]def invoice_error_from_proto(invoice_error: model_pb.InvoiceError) -> Error:
if invoice_error.reason == model_pb.InvoiceError.Reason.ALREADY_PAID:
return AlreadyPaidError()
if invoice_error.reason == model_pb.InvoiceError.Reason.WRONG_DESTINATION:
return WrongDestinationError()
if invoice_error.reason == model_pb.InvoiceError.Reason.SKU_NOT_FOUND:
return SkuNotFoundError()
return Error(f'unknown invoice error reason: {invoice_error.reason}')