The backend component to interface with the smart contract.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 line
3.9 KiB

  1. import asyncio
  2. from stellar_sdk import Keypair, Network, SorobanServer, TransactionBuilder, xdr as stellar_xdr
  3. from stellar_sdk.exceptions import PrepareTransactionException
  4. from stellar_sdk.soroban_rpc import GetTransactionStatus, SendTransactionStatus
  5. from stellar_sdk.xdr import SCVal, SCValType
  6. class SingletonMeta(type):
  7. _instances = {}
  8. def __call__(cls, *args, **kwargs):
  9. if cls not in cls._instances:
  10. instance = super().__call__(*args, **kwargs)
  11. cls._instances[cls] = instance
  12. return cls._instances[cls]
  13. class SmartContract(metaclass=SingletonMeta):
  14. contract_id: str
  15. user_key: str
  16. def __init__(self, contract_id: str, user_key: str):
  17. self.contract_id = contract_id
  18. self.user_key = user_key
  19. async def _execute_procedure(self, procedure_name: str, parameters=None):
  20. source_keypair = Keypair.from_secret(self.user_key)
  21. soroban_server = SorobanServer('https://soroban-testnet.stellar.org')
  22. source_account = soroban_server.load_account(source_keypair.public_key)
  23. built_transaction = (
  24. TransactionBuilder(
  25. source_account=source_account,
  26. base_fee=100,
  27. network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
  28. )
  29. .append_invoke_contract_function_op(
  30. contract_id=self.contract_id,
  31. function_name=procedure_name,
  32. parameters=parameters,
  33. )
  34. .set_timeout(30)
  35. .build()
  36. )
  37. try:
  38. prepared_transaction = soroban_server.prepare_transaction(built_transaction)
  39. except PrepareTransactionException as e:
  40. print(f"Exception preparing transaction: {e}\n{e.simulate_transaction_response.error}")
  41. raise e
  42. prepared_transaction.sign(source_keypair)
  43. send_response = soroban_server.send_transaction(prepared_transaction)
  44. if send_response.status != SendTransactionStatus.PENDING:
  45. raise Exception("sending transaction failed")
  46. while True:
  47. get_response = soroban_server.get_transaction(send_response.hash)
  48. if get_response.status != GetTransactionStatus.NOT_FOUND:
  49. break
  50. await asyncio.sleep(2)
  51. print(f"get_transaction response: {get_response}")
  52. if get_response.status == GetTransactionStatus.SUCCESS:
  53. assert get_response.result_meta_xdr is not None
  54. transaction_meta = stellar_xdr.TransactionMeta.from_xdr(
  55. get_response.result_meta_xdr
  56. )
  57. return_value = transaction_meta.v3.soroban_meta.return_value
  58. output = translate_soroban_value(return_value)
  59. return output
  60. else:
  61. print(f"Transaction failed: {get_response.result_xdr}")
  62. def translate_soroban_value(val: SCVal) -> int or str or bool or list[int or str or bool] or None:
  63. def sanitize(k: str) -> str:
  64. return k.lstrip('b\'').rstrip('\'')
  65. type_handlers = {
  66. SCValType.SCV_U32: lambda v: v.u32.uint32,
  67. SCValType.SCV_I32: lambda v: v.i32.int32,
  68. SCValType.SCV_U64: lambda v: v.u64.uint64,
  69. SCValType.SCV_I64: lambda v: v.i64.int64,
  70. SCValType.SCV_BOOL: lambda v: v.bool.boolean,
  71. SCValType.SCV_STRING: lambda v: sanitize(str(v.str.sc_string)),
  72. SCValType.SCV_SYMBOL: lambda v: sanitize(str(v.sym.sc_symbol)),
  73. SCValType.SCV_BYTES: lambda v: bytes(v.bytes.sc_bytes),
  74. SCValType.SCV_VEC: lambda v: [translate_soroban_value(item) for item in v.vec.sc_vec],
  75. SCValType.SCV_MAP: lambda v: {translate_soroban_value(item.key): translate_soroban_value(item.val) for item in v.map.sc_map},
  76. }
  77. if val.type == SCValType.SCV_VOID:
  78. return None
  79. handler = type_handlers.get(val.type)
  80. if handler:
  81. return handler(val)
  82. else:
  83. raise ValueError(f"Unsupported SCVal type: {val.type}")