diff --git a/.gitmodules b/.gitmodules index 453659cf..6c53554b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ path = build-containers url = git@github.com:godotengine/build-containers.git branch = main +[submodule "GodotSolanaSDKDemoPackage"] + path = GodotSolanaSDKDemoPackage + url = git@github.com:ZenRepublic/GodotSolanaSDKDemoPackage.git diff --git a/include/honeycomb.hpp b/include/honeycomb.hpp new file mode 100644 index 00000000..264a56fd --- /dev/null +++ b/include/honeycomb.hpp @@ -0,0 +1,132 @@ +#ifndef GODOT_SOLANA_SDK_HONEYCOMB_HPP +#define GODOT_SOLANA_SDK_HONEYCOMB_HPP + +#include +#include +#include +#include "transaction.hpp" + +namespace godot{ + +enum ResourceStorageEnum{ + AccountState = 0, + LedgerState = 1, +}; + +class InitResourceInput: public Resource{ +GDCLASS(InitResourceInput, Resource) +private: + String resource_name = ""; + String symbol = ""; + String uri = ""; + int32_t decimals = 0; + ResourceStorageEnum storage = AccountState; +protected: + static void _bind_methods(); +public: + void set_resource_name(const String& resource_name); + String get_resource_name(); + void set_symbol(const String& symbol); + String get_symbol(); + void set_uri(const String& uri); + String get_uri(); + void set_decimals(int32_t decimals); + int32_t get_decimals(); + void set_storage(int32_t storage); + int32_t get_storage(); + + Dictionary to_dict(); +}; + + +class UserInfoInput: public Resource{ +GDCLASS(UserInfoInput, Resource) +private: + String username = ""; + String real_name = ""; + String bio = ""; + String pfp = ""; +protected: + static void _bind_methods(); +public: + Dictionary to_dict(); +}; + + +class BasicTreeConfig : public Resource{ +GDCLASS(BasicTreeConfig, Resource) +private: + int32_t num_assets = 0; +protected: + static void _bind_methods(); +public: +}; + +class AdvancedTreeConfig : public Resource{ +GDCLASS(AdvancedTreeConfig, Resource) +private: + int32_t max_depth = 0; + int32_t max_buffer_size = 0; + int32_t canopy_depth = 0; +protected: + static void _bind_methods(); +public: + Dictionary to_dict(); +}; + +class TreeSetupConfig : public Resource{ +GDCLASS(TreeSetupConfig, Resource) +private: + Variant basic; + Variant advanced; +protected: + static void _bind_methods(); +public: + Dictionary to_dict(); +}; + +class HoneyComb: public Node{ +GDCLASS(HoneyComb, Node) +private: + bool pending = false; + Node* child = nullptr; + HTTPRequest* api_request; + Transaction* result_tx; + + String request; + String method_name; + Array args; + + Array signers; + String query_fields = ""; + + String build(); + void send_query(); + + void bind_method_from_ref(const String ref); + void add_arg(const String& name, const String& type_name, const Variant& value, bool optional = false); + void query_response_callback(int result, int response_code, const PackedStringArray& headers, const PackedByteArray& body); + void transaction_response_callback(const Dictionary& response); + + static void bind_non_changing_methods(); + +protected: + HoneyComb(); + static void _bind_methods(); + ~HoneyComb(); +public: + + Variant create_project(const Variant& authority, const String& name); + void create_user(const Variant& user_wallet_keypair); + void create_profile(const Variant& project, const Variant& payer); + void create_resource(const Variant& project, const Variant& authority, const String& name, const String& uri, const String& symbol, uint32_t decimals); + Variant createCreateNewResourceTransaction(const Variant& project, const Variant& authority, Variant params, String delegateAuthority = "", String payer = "", PackedStringArray lutAddresses = PackedStringArray(), int32_t computeUnitPrice = -1); + Variant createCreateNewResourceTreeTransaction(const Variant& project, const Variant& resource, const Variant& authority, Variant treeConfig, const Variant& delegateAuthority = "", const Variant& payer = "", PackedStringArray lutAddresses = PackedStringArray(), int32_t computeUnitPrice = -1); + Variant createMintResourceTransaction(const Variant& resource, const Variant& owner, const Variant& authority, int64_t amount, const Variant& delegateAuthority = "", const Variant& payer = "", PackedStringArray lutAddresses = PackedStringArray(), int32_t computeUnitPrice = -1); + Variant createBurnResourceTransaction(const Variant& resource, int64_t amount, const Variant& authority, const Variant& owner = "", const Variant& payer = "", const Variant& delegateAuthority = "", PackedStringArray lutAddresses = PackedStringArray(), int32_t computeUnitPrice = -1); + Variant createNewUserTransaction(const Variant& wallet, Variant info = Variant(nullptr), const Variant& payer = "", PackedStringArray lutAddresses = PackedStringArray(), int32_t computeUnitPrice = -1); +}; + +} + +#endif \ No newline at end of file diff --git a/include/shdwdrive.hpp b/include/shdwdrive.hpp new file mode 100644 index 00000000..b78035c8 --- /dev/null +++ b/include/shdwdrive.hpp @@ -0,0 +1,132 @@ +#ifndef GODOT_SOLANA_SDK_SHDW_DRIVE_HPP +#define GODOT_SOLANA_SDK_SHDW_DRIVE_HPP + +#include +#include +#include +#include "solana_client.hpp" +#include "transaction.hpp" + +namespace godot{ + +class StorageAccountV2: public Resource{ +GDCLASS(StorageAccountV2, Resource) +private: + bool immutable = false; + bool to_be_deleted = false; + uint32_t delete_request_epoch = 0; + uint64_t storage = 0; + Variant owner1; + uint32_t account_counter_seed = 0; + uint32_t creation_time = 0; + uint32_t creation_epoch = 0; + uint32_t last_fee_epoch = 0; + String identifier = ""; + + static PackedByteArray discriminator(); + +protected: + static void _bind_methods(); +public: + void from_bytes(const PackedByteArray& bytes); + String get_identifier(); + Dictionary to_dict(); +}; + +class UserInfo: public Resource{ +GDCLASS(UserInfo, Resource) +private: + uint32_t account_counter = 0; + uint32_t delete_counter = 0; + bool agreed_to_terms = false; + bool had_bad_scam_scan = false; + + static PackedByteArray discriminator(); + +protected: + static void _bind_methods(); + +public: + uint32_t get_account_counter(); + void from_bytes(const PackedByteArray& bytes); +}; + +class ShdwDrive : public Node { +GDCLASS(ShdwDrive, Node) +private: + static const std::string ID; + + bool new_user = false; + Variant owner_keypair; + String storage_name; + uint64_t storage_size; + UserInfo* user_info = nullptr; + StorageAccountV2* storage_account = nullptr; + RpcSingleHttpRequestClient *api_request = nullptr; + HTTPRequest *upload_file_request = nullptr; + + Array storage_list; + PackedStringArray storage_name_list; + PackedStringArray storage_key_list; + + SolanaClient *create_storage_account_client = nullptr; + SolanaClient *fetch_user_info_client = nullptr; + SolanaClient *fetch_storage_account_client = nullptr; + SolanaClient *fetch_all_storage_accounts_client = nullptr; + Transaction *create_storage_account_transaction = nullptr; + + static PackedByteArray initialize_accountv2_discriminator(); + static PackedByteArray create_form_line(const String& line); + static PackedByteArray create_form_line(const PackedByteArray& content); + static String get_upload_message(const Variant& storage_account_key, const String& filename_hash); + static String get_filename_hash(const String& filename); + + void get_all_storage_accounts(const Variant& owner_key); + void send_fetch_account_infos(); + + void fetch_userinfo_callback(const Dictionary& params); + void fetch_storage_account_callback(const Dictionary& params); + void upload_file_callback(int result, int response_code, const PackedStringArray& headers, const PackedByteArray& body); + + uint64_t human_size_to_bytes(const String& human_size); + + Variant get_uploader(); + +protected: + static void _bind_methods(); +public: + ShdwDrive(); + void _process(double delta) override; + + Array get_cached_storage_accounts(); + + Variant fetch_user_info(const Variant address); + Variant fetch_storage_account(const Variant address); + Variant create_storage_account(const Variant& owner_keypair, const String& name, const Variant& size); + + Variant fetch_storage_key_by_name_callback(const String& storage_name); + Variant fetch_storage_key_by_name(const Variant& owner_keypair, const String& storage_name); + + void send_create_storage_tx(); + void send_create_storage_tx_signed(); + + void create_store_api_response(const Dictionary& response); + void get_multiple_accounts_callback(const Dictionary& response); + + static Variant new_user_info_pubkey(const Variant& base_keypair); + static Variant new_stake_account_pubkey(const Variant& storage_account); + static Variant new_storage_config_pubkey(); + static Variant new_storage_account_pubkey(const Variant& owner_key, uint64_t account_seed); + + Variant initialize_account(const Variant& owner_keypair, const String name, uint64_t storage); + void upload_file_to_storage(const String& filename, const Variant& storage_owner_keypair, const Variant& storage_account); + + static Variant get_pid(); + + ~ShdwDrive(); +}; + +} // godot + + +#endif \ No newline at end of file diff --git a/include/solana_client/solana_client.hpp b/include/solana_client/solana_client.hpp index 7779c16f..18e4524a 100644 --- a/include/solana_client/solana_client.hpp +++ b/include/solana_client/solana_client.hpp @@ -57,7 +57,6 @@ class SolanaClient : public Node { Callable ws_callback; Callable rpc_callback = callable_mp(this, &SolanaClient::response_callback); - String ws_from_http(const String& http_url); String get_real_url(); uint32_t get_real_http_port(); @@ -162,6 +161,7 @@ class SolanaClient : public Node { * * @param url */ + void set_url_override(const String& url); /** diff --git a/include/transaction/message.hpp b/include/transaction/message.hpp index 7c654f95..67d58344 100644 --- a/include/transaction/message.hpp +++ b/include/transaction/message.hpp @@ -20,6 +20,8 @@ class Message: public Resource{ // Message TypedArray compiled_instructions; Array signers; + bool is_versioned_transaction = false; + Array address_lookup_tables; TypedArray merged_metas; diff --git a/include/transaction/transaction.hpp b/include/transaction/transaction.hpp index fdd83be8..fb0301b7 100644 --- a/include/transaction/transaction.hpp +++ b/include/transaction/transaction.hpp @@ -119,7 +119,7 @@ class Transaction : public Node { Error sign(); void send(); Variant sign_and_send(); - Error partially_sign(const Variant& latest_blockhash); + Error partially_sign(const Array& array); void create_signed_with_payer(Array instructions, Variant payer, Array signers, Variant latest_blockhash); diff --git a/src/honeycomb.cpp b/src/honeycomb.cpp new file mode 100644 index 00000000..00b07ae2 --- /dev/null +++ b/src/honeycomb.cpp @@ -0,0 +1,320 @@ +#include "honeycomb.hpp" + +#include "pubkey.hpp" +#include "transaction.hpp" +#include "solana_utils.hpp" +#include "godot_cpp/classes/json.hpp" + +namespace godot{ + +void InitResourceInput::_bind_methods(){ + +} + +void InitResourceInput::set_resource_name(const String& resource_name){ + this->resource_name = resource_name; +} +String InitResourceInput::get_resource_name(){ + return resource_name; +} + +void InitResourceInput::set_symbol(const String& symbol){ + this->symbol = symbol; +} +String InitResourceInput::get_symbol(){ + return symbol; +} + +void InitResourceInput::set_uri(const String& uri){ + this->uri = uri; +} +String InitResourceInput::get_uri(){ + return uri; +} + +void InitResourceInput::set_decimals(int32_t decimals){ + this->decimals = decimals; +} +int32_t InitResourceInput::get_decimals(){ + return decimals; +} + +void InitResourceInput::set_storage(int32_t storage){ + this->storage = (ResourceStorageEnum)storage; +} +int32_t InitResourceInput::get_storage(){ + return storage; +} + +Dictionary InitResourceInput::to_dict(){ + Dictionary result; + String resource_name = ""; + String symbol = ""; + String uri = ""; + int32_t decimals = 0; + ResourceStorageEnum storage = AccountState; + result["name"] = resource_name; + result["symbol"] = symbol; + result["uri"] = uri; + result["decimals"] = decimals; + if(storage == 0){ + result["storage"] = "AccountState"; + } + else{ + result["storage"] = "LedgerState"; + } + return result; +} + +void UserInfoInput::_bind_methods(){ + +} + +Dictionary UserInfoInput::to_dict(){ + Dictionary result; + + return result; +} + +void BasicTreeConfig::_bind_methods(){ + +} + +void AdvancedTreeConfig::_bind_methods(){ + +} + +Dictionary AdvancedTreeConfig::to_dict(){ + return Dictionary(); +} + +void TreeSetupConfig::_bind_methods(){ + +} + +Dictionary TreeSetupConfig::to_dict(){ + return Dictionary(); +} + +void HoneyComb::query_response_callback(int result, int response_code, const PackedStringArray& headers, const PackedByteArray& body){ + Dictionary response = JSON::parse_string(body.get_string_from_ascii()); + + Dictionary method_response = ((Dictionary)response["data"])[method_name]; + String encoded_transaction = ""; + if (method_response.has("tx")){ + encoded_transaction = ((Dictionary)method_response["tx"])["transaction"]; + } + else{ + encoded_transaction = method_response["transaction"]; + } + //String encoded_transaction = response.find_key("transaction"); + + if(encoded_transaction.is_empty()){ + pending = false; + ERR_FAIL_EDMSG("transaction is empty."); + } + + if(!result_tx->is_inside_tree()){ + memfree(result_tx); + } + else{ + remove_child(result_tx); + } + PackedByteArray decoded_tx = SolanaUtils::bs58_decode(encoded_transaction); + result_tx = (Transaction*)(Object*)Transaction::new_from_bytes(decoded_tx); + add_child(result_tx, false, INTERNAL_MODE_BACK); + //result_tx->update_latest_blockhash(""); + + result_tx->set_signers(signers); + + Callable callback = Callable(this, "transaction_response_callback"); + result_tx->connect("transaction_response_received", callback, CONNECT_ONE_SHOT); + + for(unsigned int i = 0; i < signers.size(); i++){ + if(signers[i].get_type() == Variant::OBJECT){ + if(((Object*)signers[i])->get_class() == "Keypair"){ + Array signer_array; + signer_array.append(signers[i]); + result_tx->partially_sign(signer_array); + } + } + } + + //result_tx->sign(); + result_tx->send(); +} + +void HoneyComb::transaction_response_callback(const Dictionary& response){ + + PackedByteArray bt = result_tx->serialize(); + + pending = false; + remove_child(child); + emit_signal("transaction_response_received", response); +} + + +void HoneyComb::send_query(){ + const String HONEYCOMB_URL = "https://edge.test.honeycombprotocol.com/"; + + Callable callback = Callable(this, "query_response_callback"); + api_request->connect("request_completed", callback); + + PackedStringArray headers; + headers.append("content-type: application/json"); + add_child(api_request); + child = api_request; + pending = true; + api_request->request(HONEYCOMB_URL, headers, HTTPClient::METHOD_POST, build()); +} + +String HoneyComb::build(){ + String args_type_list = ""; + String args_list = ""; + String args_values = ""; + for(unsigned int i = 0; i < args.size(); i++){ + Array format_params; + Dictionary arg = args[i]; + format_params.append(arg["name"]); + format_params.append(arg["type"]); + args_type_list += String("${0}: {1},").format(format_params); + + format_params.clear(); + format_params.append(arg["name"]); + args_list += String("{0}: ${0},").format(format_params); + + format_params.clear(); + format_params.append(arg["name"]); + format_params.append(arg["value"]); + if(String(arg["value"]).begins_with("{")){ + args_values += String("\"{0}\": {1},").format(format_params); + } + else{ + args_values += String("\"{0}\": \"{1}\",").format(format_params); + } + } + + // pop last , + args_type_list = args_type_list.erase(args_type_list.length() -1); + args_list = args_list.erase(args_list.length() -1); + args_values = args_values.erase(args_values.length() -1); + + Array format_params; + format_params.append(method_name); + format_params.append(args_type_list); + format_params.append(args_list); + format_params.append(args_values); + format_params.append(query_fields); + + const String query_template = "{ \"query\":\"query {0}({1}) { {0}({2}) { {4} } }\", \"variables\":{{3}}}"; + return query_template.format(format_params); +} + +void HoneyComb::bind_method_from_ref(const String ref){ + +} + +void HoneyComb::add_arg(const String& name, const String& type_name, const Variant& value, bool optional){ + Dictionary entry; + entry["name"] = name; + if(!optional){ + entry["type"] = type_name + String("!"); + } + else{ + entry["type"] = type_name; + } + entry["value"] = value; + args.append(entry); +} + +HoneyComb::HoneyComb(){ + api_request = memnew(HTTPRequest); + result_tx = memnew(Transaction); +} + +Variant HoneyComb::create_project(const Variant& authority, const String& name){ + signers.append(authority); + method_name = "createCreateProjectTransaction"; + + add_arg("authority", "String", Pubkey::string_from_variant(authority)); + add_arg("name", "String", name); + + query_fields = "tx { transaction blockhash lastValidBlockHeight } project"; + + send_query(); + + return nullptr; +} + +void HoneyComb::create_user(const Variant& user_wallet_key){ + signers.append(user_wallet_key); + signers.append(Pubkey::new_random()); + method_name = "createNewUserTransaction"; + + add_arg("wallet", "String", Pubkey::string_from_variant(user_wallet_key)); + //String user_info = "{\"username\": \"xyz789\",\"name\": \"abc123\",\"bio\": \"abc123\",\"pfp\": \"abc123\"}"; + Dictionary user_info; + user_info["username"] = "axel"; + user_info["name"] = "axel"; + user_info["bio"] = "axel"; + user_info["pfp"] = "axel"; + add_arg("info", "UserInfoInput", user_info, true); + + query_fields = "transaction blockhash lastValidBlockHeight"; + + send_query(); +} + +void HoneyComb::create_profile(const Variant& project, const Variant& payer){ + signers.append(payer); + method_name = "createNewProfileTransaction"; + + add_arg("project", "String", Pubkey::string_from_variant(project)); + add_arg("payer", "String", Pubkey::string_from_variant(payer)); + + query_fields = "transaction blockhash lastValidBlockHeight"; + + send_query(); +} + +void HoneyComb::create_resource(const Variant& project, const Variant& authority, const String& name, const String& uri, const String& symbol, uint32_t decimals){ + signers.append(authority); + method_name = "createCreateNewResourceTransaction"; + + add_arg("project", "String", Pubkey::string_from_variant(project)); + add_arg("authority", "String", Pubkey::string_from_variant(authority)); + String resource_info = "{\"name\": \"{0}\",\"symbol\": \"{1}\",\"uri\": \"{2}\",\"decimals\": {3}, \"storage\": \"{4}\"}"; + Array params; + params.append(name); + params.append(symbol); + params.append(uri); + params.append(decimals); + params.append("AccountState"); + add_arg("params", "InitResourceInput", resource_info.format(params)); + + query_fields = "transaction blockhash lastValidBlockHeight"; + + send_query(); +} + +void HoneyComb::bind_non_changing_methods(){ + ClassDB::add_signal("HoneyComb", MethodInfo("transaction_response_received", PropertyInfo(Variant::DICTIONARY, "response"))); + + ClassDB::bind_method(D_METHOD("create_project", "authority", "name"), &HoneyComb::create_project); + ClassDB::bind_method(D_METHOD("create_user", "user_wallet_key"), &HoneyComb::create_user); + ClassDB::bind_method(D_METHOD("create_profile", "project", "payer"), &HoneyComb::create_profile); + + ClassDB::bind_method(D_METHOD("query_response_callback", "result", "response_code", "headers", "body"), &HoneyComb::query_response_callback); + ClassDB::bind_method(D_METHOD("transaction_response_callback", "response"), &HoneyComb::transaction_response_callback); +} + +HoneyComb::~HoneyComb(){ + /*if(!api_request->is_inside_tree()){ + memfree(api_request); + } + if(!result_tx->is_inside_tree()){ + memfree(result_tx); + }*/ +} + +} \ No newline at end of file diff --git a/src/honeycomb_generated.cpp b/src/honeycomb_generated.cpp new file mode 100644 index 00000000..f4db84fd --- /dev/null +++ b/src/honeycomb_generated.cpp @@ -0,0 +1,181 @@ +#include "honeycomb.hpp" + +namespace godot{ + +Variant HoneyComb::createCreateNewResourceTransaction(const Variant& project, const Variant& authority, Variant params, String delegateAuthority, String payer, PackedStringArray lutAddresses, int32_t computeUnitPrice){ + if(pending){ + return ERR_BUSY; + } + signers.append(authority); + signers.append(Pubkey::new_from_string("11111111111111111111111111111111")); + + add_arg("project", "String", Pubkey::string_from_variant(project), false); + add_arg("authority", "String", Pubkey::string_from_variant(authority), false); + add_arg("params", "InitResourceInput", Object::cast_to(params)->to_dict(), false); + if(delegateAuthority != ""){ + add_arg("delegateAuthority", "String", Pubkey::string_from_variant(delegateAuthority), true); + } + if(payer != ""){ + add_arg("payer", "String", Pubkey::string_from_variant(payer), true); + } + if(lutAddresses != PackedStringArray()){ + add_arg("lutAddresses", "[String!]", lutAddresses, true); + } + if(computeUnitPrice != -1){ + add_arg("computeUnitPrice", "Int", computeUnitPrice, true); + } + + + method_name = "createCreateNewResourceTransaction"; + + + query_fields = " tx { transaction blockhash lastValidBlockHeight } resource "; + send_query(); + return OK; +} + +Variant HoneyComb::createCreateNewResourceTreeTransaction(const Variant& project, const Variant& resource, const Variant& authority, Variant treeConfig, const Variant& delegateAuthority, const Variant& payer, PackedStringArray lutAddresses, int32_t computeUnitPrice){ + if(pending){ + return ERR_BUSY; + } + signers.append(authority); + signers.append(delegateAuthority); + signers.append(payer); + + add_arg("project", "String", Pubkey::string_from_variant(project), false); + add_arg("resource", "String", Pubkey::string_from_variant(resource), false); + add_arg("authority", "String", Pubkey::string_from_variant(authority), false); + add_arg("treeConfig", "TreeSetupConfig", Object::cast_to(treeConfig)->to_dict(), false); + if(delegateAuthority != Variant(nullptr)){ + add_arg("delegateAuthority", "String", Pubkey::string_from_variant(delegateAuthority), true); + } + if(payer != Variant(nullptr)){ + add_arg("payer", "String", Pubkey::string_from_variant(payer), true); + } + if(lutAddresses != PackedStringArray()){ + add_arg("lutAddresses", "[String!]", lutAddresses, true); + } + if(computeUnitPrice != -1){ + add_arg("computeUnitPrice", "Int", computeUnitPrice, true); + } + + + method_name = "createCreateNewResourceTreeTransaction"; + + + query_fields = " tx { transaction blockhash lastValidBlockHeight } treeAddress proofBytes space cost maxTreeCapacity "; + send_query(); + return OK; +} + +Variant HoneyComb::createMintResourceTransaction(const Variant& resource, const Variant& owner, const Variant& authority, int64_t amount, const Variant& delegateAuthority, const Variant& payer, PackedStringArray lutAddresses, int32_t computeUnitPrice){ + if(pending){ + return ERR_BUSY; + } + signers.append(owner); + signers.append(authority); + signers.append(delegateAuthority); + signers.append(payer); + + add_arg("resource", "String", Pubkey::string_from_variant(resource), false); + add_arg("owner", "String", Pubkey::string_from_variant(owner), false); + add_arg("authority", "String", Pubkey::string_from_variant(authority), false); + add_arg("amount", "BigInt", amount, false); + if(delegateAuthority != Variant(nullptr)){ + add_arg("delegateAuthority", "String", Pubkey::string_from_variant(delegateAuthority), true); + } + if(payer != Variant(nullptr)){ + add_arg("payer", "String", Pubkey::string_from_variant(payer), true); + } + if(lutAddresses != PackedStringArray()){ + add_arg("lutAddresses", "[String!]", lutAddresses, true); + } + if(computeUnitPrice != -1){ + add_arg("computeUnitPrice", "Int", computeUnitPrice, true); + } + + + method_name = "createMintResourceTransaction"; + + + query_fields = " transaction blockhash lastValidBlockHeight "; + send_query(); + return OK; +} + +Variant HoneyComb::createBurnResourceTransaction(const Variant& resource, int64_t amount, const Variant& authority, const Variant& owner, const Variant& payer, const Variant& delegateAuthority, PackedStringArray lutAddresses, int32_t computeUnitPrice){ + if(pending){ + return ERR_BUSY; + } + signers.append(authority); + signers.append(owner); + signers.append(payer); + signers.append(delegateAuthority); + + add_arg("resource", "String", Pubkey::string_from_variant(resource), false); + add_arg("amount", "BigInt", amount, false); + add_arg("authority", "String", Pubkey::string_from_variant(authority), false); + if(owner != Variant(nullptr)){ + add_arg("owner", "String", Pubkey::string_from_variant(owner), true); + } + if(payer != Variant(nullptr)){ + add_arg("payer", "String", Pubkey::string_from_variant(payer), true); + } + if(delegateAuthority != Variant(nullptr)){ + add_arg("delegateAuthority", "String", Pubkey::string_from_variant(delegateAuthority), true); + } + if(lutAddresses != PackedStringArray()){ + add_arg("lutAddresses", "[String!]", lutAddresses, true); + } + if(computeUnitPrice != -1){ + add_arg("computeUnitPrice", "Int", computeUnitPrice, true); + } + + + method_name = "createBurnResourceTransaction"; + + + query_fields = " transaction blockhash lastValidBlockHeight "; + send_query(); + return OK; +} + +Variant HoneyComb::createNewUserTransaction(const Variant& wallet, Variant info, const Variant& payer, PackedStringArray lutAddresses, int32_t computeUnitPrice){ + if(pending){ + return ERR_BUSY; + } + signers.append(wallet); + signers.append(payer); + + add_arg("wallet", "String", Pubkey::string_from_variant(wallet), false); + if(info != Variant(nullptr)){ + add_arg("info", "UserInfoInput", Object::cast_to(info)->to_dict(), true); + } + if(payer != Variant(nullptr)){ + add_arg("payer", "String", Pubkey::string_from_variant(payer), true); + } + if(lutAddresses != PackedStringArray()){ + add_arg("lutAddresses", "[String!]", lutAddresses, true); + } + if(computeUnitPrice != -1){ + add_arg("computeUnitPrice", "Int", computeUnitPrice, true); + } + + + method_name = "createNewUserTransaction"; + + + query_fields = " transaction blockhash lastValidBlockHeight "; + send_query(); + return OK; +} + +void HoneyComb::_bind_methods(){ + bind_non_changing_methods(); + ClassDB::bind_method(D_METHOD("create_new_resource", "project", "authority", "params", "delegateAuthority", "payer", "lutAddresses", "computeUnitPrice"), &HoneyComb::createCreateNewResourceTransaction, DEFVAL(""), DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("create_new_resource_tree", "project", "resource", "authority", "treeConfig", "delegateAuthority", "payer", "lutAddresses", "computeUnitPrice"), &HoneyComb::createCreateNewResourceTreeTransaction, DEFVAL(""), DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("mint_resource", "resource", "owner", "authority", "amount", "delegateAuthority", "payer", "lutAddresses", "computeUnitPrice"), &HoneyComb::createMintResourceTransaction, DEFVAL(""), DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("burn_resource", "resource", "amount", "authority", "owner", "payer", "delegateAuthority", "lutAddresses", "computeUnitPrice"), &HoneyComb::createBurnResourceTransaction, DEFVAL(""), DEFVAL(""), DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("new_user", "wallet", "info", "payer", "lutAddresses", "computeUnitPrice"), &HoneyComb::createNewUserTransaction, DEFVAL(Variant(nullptr)), DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL(-1)); +} +} // godot diff --git a/src/register_types.cpp b/src/register_types.cpp index 0079ad3d..4737d7a6 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -28,6 +28,8 @@ #include "rpc_single_http_request_client.hpp" #include "rpc_multi_http_request_client.hpp" #include "address_lookup_table.hpp" +#include "shdwdrive.hpp" +#include "honeycomb.hpp" #include #include @@ -92,8 +94,12 @@ void initialize_solana_sdk_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); add_setting("solana_sdk/client/default_url", Variant::Type::STRING, "https://api.devnet.solana.com"); add_setting("solana_sdk/client/default_http_port", Variant::Type::INT, 443); diff --git a/src/shdwdrive.cpp b/src/shdwdrive.cpp new file mode 100644 index 00000000..2194776e --- /dev/null +++ b/src/shdwdrive.cpp @@ -0,0 +1,622 @@ +#include "shdwdrive.hpp" + +#include "solana_client.hpp" +#include +#include +#include +#include +#include +#include + +#include "solana_utils.hpp" + +#include +#include +#include +#include + +#include "sha256.hpp" + +namespace godot{ + +const std::string ShdwDrive::ID = "2e1wdyNhUvE76y6yUCvah2KaviavMJYKoRun8acMRBZZ"; + + +PackedByteArray StorageAccountV2::discriminator(){ + const unsigned char DISCRIMINATOR_ARRAY[] = {133, 53, 253, 82, 212, 5, 201, 218}; + PackedByteArray result; + for (unsigned int i = 0; i < 8; i++){ + result.append(DISCRIMINATOR_ARRAY[i]); + } + return result; +} + +void StorageAccountV2::_bind_methods(){ +} + +void StorageAccountV2::from_bytes(const PackedByteArray& bytes){ + const int64_t MINIMUM_STORAGE_ACCOUNT_SIZE = 66; + ERR_FAIL_COND_EDMSG(bytes.size() < 66, "Invalid Storage Account"); + ERR_FAIL_COND_EDMSG(bytes.slice(0, 8) != discriminator(), "Account is not StorageAccountV2."); + + int c = 8; + immutable = bytes[c++]; + to_be_deleted = bytes[c++]; + delete_request_epoch = bytes.decode_u32(c); + c += 4; + storage = bytes.decode_u64(c); + c += 8; + owner1 = Pubkey::new_from_bytes(bytes.slice(c, c + 32)); + c += 32; + account_counter_seed = bytes.decode_u32(c); + c += 4; + creation_time = bytes.decode_u32(c); + c += 4; + creation_epoch = bytes.decode_u32(c); + c += 4; + last_fee_epoch = bytes.decode_u32(c); + c += 4; + uint32_t identifier_size = bytes.decode_u32(c); + c += 4; + const PackedByteArray identifier_bytes = bytes.slice(c); + + ERR_FAIL_COND_EDMSG(identifier_bytes.size() < identifier_size, "Invalid Storage Account"); + identifier = identifier_bytes.get_string_from_ascii(); +} + +String StorageAccountV2::get_identifier(){ + return identifier; +} + +Dictionary StorageAccountV2::to_dict(){ + Dictionary result; + result["immutable"] = immutable; + result["to_be_deleted"] = to_be_deleted; + result["delete_request_epoch"] = delete_request_epoch; + result["storage"] = storage; + result["owner1"] = owner1; + result["account_counter_seed"] = account_counter_seed; + result["creation_time"] = creation_time; + result["creation_epoch"] = creation_epoch; + result["last_fee_epoch"] = last_fee_epoch; + result["identifier"] = identifier; + + return result; +} + +PackedByteArray UserInfo::discriminator(){ + const unsigned char DISCRIMINATOR_ARRAY[] = {83, 134, 200, 56, 144, 56, 10, 62}; + PackedByteArray result; + for (unsigned int i = 0; i < 8; i++){ + result.append(DISCRIMINATOR_ARRAY[i]); + } + return result; +} + +void UserInfo::_bind_methods(){ + ClassDB::bind_method(D_METHOD("from_bytes", "bytes"), &UserInfo::from_bytes); +} + +uint32_t UserInfo::get_account_counter(){ + return account_counter; +} + +void UserInfo::from_bytes(const PackedByteArray& bytes){ + const int64_t EXPECTED_ACCOUNT_SIZE = 18; + ERR_FAIL_COND_EDMSG(bytes.size() != EXPECTED_ACCOUNT_SIZE, "Invalid user account size."); + ERR_FAIL_COND_EDMSG(bytes.slice(0, 8) != discriminator(), "Account is not UserInfo."); + + account_counter = bytes.decode_u32(8); + delete_counter = bytes.decode_u32(12); + agreed_to_terms = bytes[16] == 1; + had_bad_scam_scan = bytes[17] == 1; +} + +ShdwDrive::ShdwDrive(){ + upload_file_request = memnew(HTTPRequest); + user_info = memnew(UserInfo); + api_request = memnew(RpcSingleHttpRequestClient); + api_request->set_skip_id(true); + + create_storage_account_client = memnew(SolanaClient); + create_storage_account_client->set_async_override(true); + create_storage_account_transaction = memnew(Transaction); + + fetch_user_info_client = memnew(SolanaClient); + fetch_user_info_client->set_async_override(true); + Callable fetch_user_info_callable = Callable(this, "fetch_userinfo_callback"); + fetch_user_info_client->set_callback(fetch_user_info_callable); + + fetch_storage_account_client = memnew(SolanaClient); + fetch_storage_account_client->set_async_override(true); + Callable fetch_storage_account_callable = Callable(this, "fetch_storage_account_callback"); + fetch_storage_account_client->set_callback(fetch_storage_account_callable); + + fetch_all_storage_accounts_client = memnew(SolanaClient); + fetch_all_storage_accounts_client->set_async_override(true); +} + +void ShdwDrive::_process(double delta){ + api_request->process(delta); + create_storage_account_transaction->_process(delta); + fetch_storage_account_client->_process(delta); + fetch_user_info_client->_process(delta); + fetch_all_storage_accounts_client->_process(delta); +} + +void ShdwDrive::send_fetch_account_infos(){ + ERR_FAIL_COND_EDMSG(user_info == nullptr, "User info is not fetched."); + + uint32_t owned_accounts = user_info->get_account_counter(); + PackedStringArray accounts; + + storage_name_list.clear(); + storage_key_list.clear(); + for(uint64_t i = 0; i < owned_accounts; i++){ + Variant storage_account = new_storage_account_pubkey(owner_keypair, i); + accounts.append(Pubkey::string_from_variant(storage_account)); + + storage_key_list.append(Pubkey::string_from_variant(storage_account)); + } + + Callable callback = Callable(this, "get_multiple_accounts_callback"); + //fetch_all_storage_accounts_client->enable_account_filter(); + fetch_all_storage_accounts_client->connect("http_response_received", callback, CONNECT_ONE_SHOT); + fetch_all_storage_accounts_client->get_multiple_accounts(accounts); +} + +void ShdwDrive::get_multiple_accounts_callback(const Dictionary& response){ + ERR_FAIL_COND_EDMSG(!response.has("result"), "Fetching storage account failed."); + ERR_FAIL_COND_EDMSG(!((Dictionary)response["result"]).has("value"), "Getting storage accounts failed."); + + Array accounts = ((Dictionary)response["result"])["value"]; + + storage_name_list.resize(storage_key_list.size()); + storage_list.resize(storage_key_list.size()); + + for(uint32_t i = 0; i < accounts.size(); i++){ + Dictionary account = accounts[i]; + StorageAccountV2* storage_account = memnew(StorageAccountV2); + String encoded_data = ((Array)account["data"])[0]; + storage_account->from_bytes(SolanaUtils::bs64_decode(encoded_data)); + + String identifier = storage_account->get_identifier(); + storage_name_list[i] = identifier; + storage_list[i] = storage_account; + } + emit_signal("all_storage_accounts_fetched"); +} + +Array ShdwDrive::get_cached_storage_accounts(){ + Array result; + for(unsigned int i = 0; i < storage_list.size(); i++){ + Dictionary dict = Object::cast_to(storage_list[i])->to_dict(); + dict["address"] = storage_key_list[i]; + result.append(dict); + } + return result; +} + +Variant ShdwDrive::fetch_user_info(const Variant address){ + UserInfo *user_info = memnew(UserInfo); + + fetch_user_info_client->get_account_info(Pubkey::string_from_variant(address)); + return OK; +} + +Variant ShdwDrive::fetch_storage_account(const Variant address){ + StorageAccountV2 *storage_account = memnew(StorageAccountV2); + + fetch_storage_account_client->get_account_info(Pubkey::string_from_variant(address)); + return OK; +} + +PackedByteArray ShdwDrive::initialize_accountv2_discriminator(){ + const unsigned char DISCRIMINATOR_ARRAY[] = {8, 182, 149, 144, 185, 31, 209, 105}; + PackedByteArray result; + for (unsigned int i = 0; i < 8; i++){ + result.append(DISCRIMINATOR_ARRAY[i]); + } + return result; +} + +uint64_t ShdwDrive::human_size_to_bytes(const String& human_size){ + if(human_size.ends_with("kb") || human_size.ends_with("KB")){ + return human_size.substr(0, human_size.length() -2).to_int() * 1000; + } + else if(human_size.ends_with("mb") || human_size.ends_with("MB")){ + return human_size.substr(0, human_size.length() -2).to_int() * 1000000; + } + else if(human_size.ends_with("gb") || human_size.ends_with("GB")){ + return human_size.substr(0, human_size.length() -2).to_int() * 1000000000; + } + else{ + ERR_FAIL_V_EDMSG(0, "Example input 10000, 10KB, 300mb, 1GB"); + } +} + +Variant ShdwDrive::get_uploader(){ + return Pubkey::new_from_string("972oJTFyjmVNsWM4GHEGPWUomAiJf2qrVotLtwnKmWem"); +} + +void ShdwDrive::get_all_storage_accounts(const Variant &owner_key){ + const unsigned int MAX_ACCOUNTS_PER_RPC_CALL = 100; + owner_keypair = owner_key; + + Callable callback = Callable(this, "send_fetch_account_infos"); + + connect("user_info_fetched", callback, CONNECT_ONE_SHOT); + //connect("storage_account_fetched", callback, CONNECT_ONE_SHOT); + + fetch_user_info(new_user_info_pubkey(owner_keypair)); + + //fetch_all_storage_accounts_client->enable_account_filter() +} + +void ShdwDrive::fetch_userinfo_callback(const Dictionary& params){ + // TODO(Virax): Add helper for this common pattern. + if(!params.has("result")){ + ERR_PRINT_ED("Fetching user info account failed."); + return; + } + if(!((Dictionary)params["result"]).has("value")){ + ERR_PRINT_ED("User is not init"); + return; + } + + Dictionary account = ((Dictionary)params["result"])["value"]; + + if(account.has("data")){ + Array encoded_data = account["data"]; + const PackedByteArray data_info = SolanaUtils::bs64_decode(encoded_data[0]); + + user_info->from_bytes(data_info); + } + else{ + new_user = true; + } + + emit_signal("user_info_fetched"); +} + +void ShdwDrive::fetch_storage_account_callback(const Dictionary& params){ + // TODO(Virax): Add helper for this common pattern. + if(!params.has("result")){ + ERR_PRINT_ED("Fetching storage account failed."); + return; + } + if(!((Dictionary)params["result"]).has("value")){ + return; + } + + Dictionary account = ((Dictionary)params["result"])["value"]; + const PackedByteArray data_info = SolanaUtils::bs64_decode(account["data"]); + + StorageAccountV2* storage_account = memnew(StorageAccountV2); + storage_account->from_bytes(data_info); + + emit_signal("storage_account_fetched", storage_account); +} + +void ShdwDrive::_bind_methods(){ + ClassDB::add_signal("ShdwDrive", MethodInfo("user_info_fetched", PropertyInfo(Variant::OBJECT, "user_info"))); + ClassDB::add_signal("ShdwDrive", MethodInfo("storage_account_fetched", PropertyInfo(Variant::OBJECT, "storage_account"))); + ClassDB::add_signal("ShdwDrive", MethodInfo("storage_account_response", PropertyInfo(Variant::DICTIONARY, "response"))); + ClassDB::add_signal("ShdwDrive", MethodInfo("upload_response", PropertyInfo(Variant::DICTIONARY, "response"))); + ClassDB::add_signal("ShdwDrive", MethodInfo("all_storage_accounts_fetched")); + + ClassDB::bind_static_method("ShdwDrive", D_METHOD("new_user_info_pubkey", "base_key"), &ShdwDrive::new_user_info_pubkey); + ClassDB::bind_static_method("ShdwDrive", D_METHOD("new_storage_config_pubkey"), &ShdwDrive::new_storage_config_pubkey); + ClassDB::bind_static_method("ShdwDrive", D_METHOD("new_stake_account_pubkey", "base_key"), &ShdwDrive::new_stake_account_pubkey); + ClassDB::bind_static_method("ShdwDrive", D_METHOD("new_storage_account_pubkey", "base_key", "account_seed"), &ShdwDrive::new_storage_account_pubkey); + + ClassDB::bind_method(D_METHOD("send_fetch_account_infos"), &ShdwDrive::send_fetch_account_infos); + ClassDB::bind_method(D_METHOD("get_all_storage_accounts", "owner_key"), &ShdwDrive::get_all_storage_accounts); + ClassDB::bind_method(D_METHOD("fetch_userinfo_callback", "params"), &ShdwDrive::fetch_userinfo_callback); + ClassDB::bind_method(D_METHOD("get_multiple_accounts_callback"), &ShdwDrive::get_multiple_accounts_callback); + ClassDB::bind_method(D_METHOD("upload_file_callback", "result", "response_code", "headers", "body"), &ShdwDrive::upload_file_callback); + ClassDB::bind_method(D_METHOD("fetch_storage_account_callback", "params"), &ShdwDrive::fetch_storage_account_callback); + ClassDB::bind_method(D_METHOD("create_storage_account", "owner_keypair", "storage_name", "storage_size"), &ShdwDrive::create_storage_account); + ClassDB::bind_method(D_METHOD("send_create_storage_tx"), &ShdwDrive::send_create_storage_tx); + ClassDB::bind_method(D_METHOD("send_create_storage_tx_signed"), &ShdwDrive::send_create_storage_tx_signed); + ClassDB::bind_method(D_METHOD("create_store_api_response", "response"), &ShdwDrive::create_store_api_response); + ClassDB::bind_method(D_METHOD("fetch_storage_key_by_name", "storage_owner", "storage_name"), &ShdwDrive::fetch_storage_key_by_name); + ClassDB::bind_method(D_METHOD("fetch_storage_key_by_name_callback", "storage_name"), &ShdwDrive::fetch_storage_key_by_name_callback); + ClassDB::bind_method(D_METHOD("get_cached_storage_accounts"), &ShdwDrive::get_cached_storage_accounts); + + ClassDB::bind_method(D_METHOD("upload_file_to_storage", "filename", "storage_owner_keypair", "storage_account"), &ShdwDrive::upload_file_to_storage); +} + +Variant ShdwDrive::create_storage_account(const Variant& owner_keypair, const String& name, const Variant& size){ + this->owner_keypair = owner_keypair; + this->storage_name = name; + if(size.get_type() == Variant::INT){ + this->storage_size = size; + } + else if(size.get_type() == Variant::STRING){ + this->storage_size = human_size_to_bytes(size); + } + else{ + ERR_FAIL_V_EDMSG(nullptr, "Invalid storage input size"); + } + + Callable callback = Callable(this, "send_create_storage_tx"); + + connect("user_info_fetched", callback, CONNECT_ONE_SHOT); + connect("storage_account_fetched", callback, CONNECT_ONE_SHOT); + + fetch_user_info(new_user_info_pubkey(owner_keypair)); + //fetch_storage_account(owner_keypair); + + return nullptr; +} + +Variant ShdwDrive::fetch_storage_key_by_name_callback(const String& storage_name){ + ERR_FAIL_COND_V_EDMSG(storage_key_list.size() != storage_name_list.size(), nullptr, "Internal Error when fetching storages."); + int64_t index = storage_name_list.find(storage_name); + if(index >= 0){ + Variant storage_account_pk = Pubkey::new_from_string(storage_key_list[index]); + emit_signal("storage_account_fetched", storage_account_pk); + return storage_key_list[index]; + } + emit_signal("storage_account_fetched", nullptr); + return nullptr; +} + +Variant ShdwDrive::fetch_storage_key_by_name(const Variant& owner_keypair, const String& storage_name){ + ERR_FAIL_COND_V_EDMSG(storage_key_list.size() != storage_name_list.size(), nullptr, "Internal Error when fetching storages."); + int64_t index = storage_name_list.find(storage_name); + + if(index >= 0){ + Variant storage_account_pk = Pubkey::new_from_string(storage_key_list[index]); + emit_signal("storage_account_fetched", storage_account_pk); + return storage_key_list[index]; + } + + connect("all_storage_accounts_fetched", Callable(this, "fetch_storage_key_by_name_callback").bind(storage_name)); + get_all_storage_accounts(owner_keypair); + return nullptr; +} + +void ShdwDrive::send_create_storage_tx(){ + ERR_FAIL_COND(user_info == nullptr && new_user == false); + //ERR_FAIL_COND(storage_account == nullptr); + + Variant instruction = initialize_account(owner_keypair, this->storage_name, this->storage_size); + + create_storage_account_transaction->set_payer(owner_keypair); + create_storage_account_transaction->add_instruction(instruction); + create_storage_account_transaction->update_latest_blockhash(); + + Array partially_signers; + partially_signers.append(owner_keypair); + + Callable callback = Callable(this, "send_create_storage_tx_signed"); + create_storage_account_transaction->connect("signer_state_changed", callback, CONNECT_ONE_SHOT); + create_storage_account_transaction->partially_sign(partially_signers); +} + +void ShdwDrive::send_create_storage_tx_signed(){ + PackedByteArray ser = create_storage_account_transaction->serialize(); + + const String SHDW_DRIVE_ENDPOINT = "https://shadow-storage.genesysgo.net:443/storage-account"; + + PackedStringArray http_headers; + http_headers.append("Content-Type: application/json"); + http_headers.append("Accept-Encoding: json"); + + Dictionary dict; + dict["transaction"] = SolanaUtils::bs64_encode(ser); + + Dictionary url = SolanaClient::parse_url(SHDW_DRIVE_ENDPOINT); + + Callable callback = Callable(this, "create_store_api_response"); + api_request->asynchronous_request(dict, url, callback); + //api_request->asynchronous_request(SHDW_DRIVE_ENDPOINT + String("/storage-account"), http_headers, HTTPClient::METHOD_POST, JSON::stringify(dict)); +} + +void ShdwDrive::create_store_api_response(const Dictionary& response){ + emit_signal("storage_account_response", response); +} + +Variant ShdwDrive::new_user_info_pubkey(const Variant& base_keypair){ + Array seeds; + seeds.append(String("user-info").to_ascii_buffer()); + seeds.append(Pubkey::bytes_from_variant(base_keypair)); + return Pubkey::new_pda_bytes(seeds, get_pid()); +} + +Variant ShdwDrive::new_storage_config_pubkey(){ + Array seeds; + seeds.append(String("storage-config").to_ascii_buffer()); + return Pubkey::new_pda_bytes(seeds, get_pid()); +} + +Variant ShdwDrive::new_stake_account_pubkey(const Variant& storage_account){ + Array seeds; + seeds.append(String("stake-account").to_ascii_buffer()); + seeds.append(Pubkey::bytes_from_variant(storage_account)); + return Pubkey::new_pda_bytes(seeds, get_pid()); +} + +Variant ShdwDrive::new_storage_account_pubkey(const Variant& owner_key, uint64_t account_seed){ + Array seeds; + seeds.append(String("storage-account").to_ascii_buffer()); + seeds.append(Pubkey::bytes_from_variant(owner_key)); + PackedByteArray account_seed_buffer; + account_seed_buffer.resize(4); + account_seed_buffer.encode_u32(0, account_seed); + seeds.append(account_seed_buffer); + + return Pubkey::new_pda_bytes(seeds, get_pid()); +} + +Variant ShdwDrive::initialize_account(const Variant& owner_keypair, const String name, uint64_t storage){ + Instruction *result = memnew(Instruction); + + PackedByteArray data = initialize_accountv2_discriminator(); + data.resize(12); + data.encode_u32(8, name.length()); + data.append_array(name.to_ascii_buffer()); + PackedByteArray storage_bytes; + storage_bytes.resize(8); + storage_bytes.encode_u64(0, storage); + data.append_array(storage_bytes); + + const Variant new_pid = memnew(Pubkey(String(ID.c_str()))); + result->set_program_id(new_pid); + result->set_data(data); + + const Variant TOKEN_MINT = Pubkey::new_from_string("SHDWyBxihqiCj6YekG2GUr7wqKLeLAMK1gHZck9pL6y"); + Variant ata_account = Pubkey::new_associated_token_address(owner_keypair, TOKEN_MINT); + const Variant STORAGE_CONFIG = new_storage_config_pubkey(); + const Variant USER_INFO_PUBKEY = new_user_info_pubkey(owner_keypair); + + uint64_t account_seed = 0; + if(!new_user){ + account_seed = user_info->get_account_counter(); + } + const Variant STORAGE_ACCOUNT = new_storage_account_pubkey(owner_keypair, account_seed); + const Variant STAKE_ACCOUNT = new_stake_account_pubkey(STORAGE_ACCOUNT); + const Variant UPLOADER = get_uploader(); + + result->append_meta(*memnew(AccountMeta(STORAGE_CONFIG, false, true))); + result->append_meta(*memnew(AccountMeta(USER_INFO_PUBKEY, false, true))); + result->append_meta(*memnew(AccountMeta(STORAGE_ACCOUNT, false, true))); + result->append_meta(*memnew(AccountMeta(STAKE_ACCOUNT, false, true))); + result->append_meta(*memnew(AccountMeta(TOKEN_MINT, false, false))); + result->append_meta(*memnew(AccountMeta(owner_keypair, true, true))); + result->append_meta(*memnew(AccountMeta(UPLOADER, true, false))); + result->append_meta(*memnew(AccountMeta(ata_account, false, true))); + + const Variant SYSTEM_PROGRAM_PID = SystemProgram::get_pid(); + const Variant TOKEN_PID = TokenProgram::get_pid(); + const Variant SYSVAR_RENT = Pubkey::new_from_string("SysvarRent111111111111111111111111111111111"); + + result->append_meta(*memnew(AccountMeta(SYSTEM_PROGRAM_PID, false, false))); + result->append_meta(*memnew(AccountMeta(TOKEN_PID, false, false))); + result->append_meta(*memnew(AccountMeta(SYSVAR_RENT, false, false))); + + return result; +} + +PackedByteArray ShdwDrive::create_form_line(const String& line){ + PackedByteArray result; + result.append_array(line.to_utf8_buffer()); + result.append_array(String("\r\n").to_utf8_buffer()); + return result; +} + +PackedByteArray ShdwDrive::create_form_line(const PackedByteArray& content){ + PackedByteArray result; + result.append_array(content); + result.append_array(String("\r\n").to_utf8_buffer()); + return result; +} + +String ShdwDrive::get_upload_message(const Variant& storage_account_key, const String& filename_hash){ + Array arr; + arr.append(Pubkey::string_from_variant(storage_account_key)); + arr.append(filename_hash); + return String("Shadow Drive Signed Message:\nStorage Account: {0}\nUpload files with hash: {1}").format(arr); +} + +String ShdwDrive::get_filename_hash(const String& filename){ + SHA256 hasher; + const PackedByteArray filename_bytes = filename.to_ascii_buffer(); + hasher.update(filename_bytes.ptr(), filename_bytes.size()); + uint8_t *sha256_hash = hasher.digest(); + + PackedByteArray res; + res.resize(32); + + for(unsigned int i = 0; i < 32; i++){ + res[i] = sha256_hash[i]; + } + + delete[] sha256_hash; + + return res.hex_encode(); +} + +void ShdwDrive::upload_file_to_storage(const String& filename, const Variant& storage_owner_keypair, const Variant& storage_account){ + const String boundary = "--GODOTSOLANASDKBOUNDARY"; + + PackedByteArray file_content = FileAccess::get_file_as_bytes(filename); + + ERR_FAIL_COND_EDMSG(file_content.is_empty(), "Failed to read file contents."); + + PackedStringArray splits = filename.split("/", false); + + String name_without_path = filename; + if(splits.size() > 1){ + name_without_path = splits[splits.size() - 1]; + } + + const String filename_hash = get_filename_hash(name_without_path); + + const String upload_message = get_upload_message(storage_account, filename_hash); + + PackedByteArray signature = Object::cast_to(storage_owner_keypair)->sign_message(upload_message.to_ascii_buffer()); + + PackedByteArray request_body; + request_body.append_array(create_form_line(boundary)); + request_body.append_array(create_form_line("Content-Disposition: form-data; name=\"file\"; filename=\"" + name_without_path + "\"")); + //request_body.append_array(create_form_line("Content-Type: text/plain")); + request_body.append_array(create_form_line("Content-Type: application/octet-stream")); + request_body.append_array(create_form_line("")); + request_body.append_array(create_form_line(file_content)); + request_body.append_array(create_form_line(boundary)); + request_body.append_array(create_form_line("Content-Disposition: form-data; name=\"message\"")); + request_body.append_array(create_form_line("")); + request_body.append_array(create_form_line(SolanaUtils::bs58_encode(signature))); + request_body.append_array(create_form_line(boundary)); + request_body.append_array(create_form_line("Content-Disposition: form-data; name=\"signer\"")); + request_body.append_array(create_form_line("")); + request_body.append_array(create_form_line(Pubkey::string_from_variant(storage_owner_keypair))); + request_body.append_array(create_form_line(boundary)); + request_body.append_array(create_form_line("Content-Disposition: form-data; name=\"storage_account\"")); + request_body.append_array(create_form_line("")); + request_body.append_array(create_form_line(Pubkey::string_from_variant(storage_account))); + request_body.append_array(create_form_line(boundary)); + request_body.append_array(create_form_line("Content-Disposition: form-data; name=\"fileNames\"")); + request_body.append_array(create_form_line("")); + request_body.append_array(create_form_line(name_without_path)); + request_body.append_array(create_form_line(boundary + String("--"))); + + PackedStringArray headers; + headers.append(String("Content-Type: multipart/form-data;boundary=") + boundary.substr(2)); + headers.append(String("accept: */*")); + headers.append("accept-encoding: gzip, br, deflate"); + headers.append(String("content-length: ") + String::num_int64(request_body.size())); + + const String SHDW_DRIVE_ENDPOINT = "https://shadow-storage.genesysgo.net:443/upload"; + //const String SHDW_DRIVE_ENDPOINT = "http://127.0.0.1:8000/upload"; + + add_child(upload_file_request, INTERNAL_MODE_BACK); + + Callable upload_file_response = Callable(this, "upload_file_callback"); + upload_file_request->connect("request_completed", upload_file_response, CONNECT_ONE_SHOT); + + upload_file_request->request_raw(SHDW_DRIVE_ENDPOINT, headers, HTTPClient::METHOD_POST, request_body); +} + +void ShdwDrive::upload_file_callback(int result, int response_code, const PackedStringArray& headers, const PackedByteArray& body){ + Dictionary response = JSON::parse_string(body.get_string_from_ascii()); + emit_signal("upload_response", response); +} + +Variant ShdwDrive::get_pid(){ + return Pubkey::new_from_string(ID.c_str()); +} + +ShdwDrive::~ShdwDrive(){ + memfree(api_request); + memfree(user_info); + memfree(create_storage_account_client); + memfree(create_storage_account_transaction); + memfree(fetch_user_info_client); + memfree(fetch_storage_account_client); +} + +} \ No newline at end of file diff --git a/src/transaction/message.cpp b/src/transaction/message.cpp index e014a33e..009c45ad 100644 --- a/src/transaction/message.cpp +++ b/src/transaction/message.cpp @@ -103,7 +103,7 @@ void Message::merge_account_meta(const AccountMeta &account_meta){ } void Message::merge_signer(const Variant& signer){ - ERR_FAIL_COND_EDMSG(Pubkey::is_pubkey(signer), "Pubkey is provided as signing parameter. (Use Keypair or WalletAdapter.)"); + //ERR_FAIL_COND_EDMSG(Pubkey::is_pubkey(signer), "Pubkey is provided as signing parameter. (Use Keypair or WalletAdapter.)"); for(unsigned int i = 0; i < signers.size(); i++){ if(Pubkey::bytes_from_variant(signers[i]) == Pubkey::bytes_from_variant(signer)){ @@ -167,7 +167,7 @@ Message::Message(const PackedByteArray& bytes){ num_required_signatures = bytes[cursor++]; - bool is_versioned_transaction = false; + is_versioned_transaction = false; if(num_required_signatures > 127){ is_versioned_transaction = true; @@ -232,6 +232,7 @@ PackedByteArray Message::serialize(){ } result.append_array(serialize_blockhash()); + PackedByteArray temp = serialize_blockhash(); result.append(compiled_instructions.size()); @@ -277,6 +278,9 @@ int Message::locate_account_meta(const TypedArray& arr, const Accou } bool Message::is_versioned(){ + if(is_versioned_transaction){ + return true; + } if(address_lookup_tables.is_empty()){ return false; } diff --git a/src/transaction/transaction.cpp b/src/transaction/transaction.cpp index 459d2bbf..c0248667 100644 --- a/src/transaction/transaction.cpp +++ b/src/transaction/transaction.cpp @@ -31,6 +31,7 @@ void Transaction::_bind_methods() { ClassDB::add_signal("Transaction", MethodInfo("transaction_response_received", PropertyInfo(Variant::DICTIONARY, "result"))); ClassDB::add_signal("Transaction", MethodInfo("blockhash_updated", PropertyInfo(Variant::DICTIONARY, "result"))); ClassDB::add_signal("Transaction", MethodInfo("blockhash_update_failed", PropertyInfo(Variant::DICTIONARY, "result"))); + ClassDB::add_signal("Transaction", MethodInfo("signer_state_changed")); ClassDB::bind_method(D_METHOD("set_url_override", "url_override"), &Transaction::set_url_override); ClassDB::bind_static_method("Transaction", D_METHOD("new_from_bytes", "bytes"), &Transaction::new_from_bytes); @@ -67,6 +68,7 @@ void Transaction::_signer_signed(PackedByteArray signature){ signatures[index] = signature; ready_signature_amount++; + emit_signal("signer_state_changed"); check_fully_signed(); } @@ -145,6 +147,7 @@ void Transaction::sign_at_index(const uint32_t index){ PackedByteArray signature = kp->sign_message(serialize_message()); signatures[index] = signature; ready_signature_amount++; + emit_signal("signer_state_changed"); check_fully_signed(); } else if(signers[index].has_method("sign_message")){ @@ -431,7 +434,7 @@ bool Transaction::get_external_payer(){ } void Transaction::update_latest_blockhash(const String &custom_hash){ - ERR_FAIL_COND_EDMSG(!is_inside_tree(), "Transaction node must be added to scene tree."); + //ERR_FAIL_COND_EDMSG(!is_inside_tree(), "Transaction node must be added to scene tree."); if(custom_hash.is_empty()){ pending_blockhash = true; @@ -539,7 +542,7 @@ void Transaction::blockhash_callback(Dictionary params){ } void Transaction::send(){ - ERR_FAIL_COND_EDMSG(!is_inside_tree(), "Transaction node must be added to scene tree."); + //ERR_FAIL_COND_EDMSG(!is_inside_tree(), "Transaction node must be added to scene tree."); ERR_FAIL_COND_EDMSG(Object::cast_to(message)->get_signers().size() != signatures.size(), "Transaction does not have enough signers."); @@ -579,7 +582,26 @@ Error Transaction::sign(){ return OK; } -Error Transaction::partially_sign(const Variant& latest_blockhash){ +Error Transaction::partially_sign(const Array& array){ + ERR_FAIL_COND_V_EDMSG(!is_message_valid(), Error::ERR_INVALID_DATA, "Invalid message."); + + Callable pending_blockhash_callback(this, "partially_sign"); + if(pending_blockhash){ + connect("send_ready", pending_blockhash_callback.bind(array), CONNECT_ONE_SHOT); + return ERR_UNAVAILABLE; + } + + signers = Object::cast_to(message)->get_signers(); + + for(unsigned int i = 0; i < array.size(); i++){ + for(unsigned int j = 0; j < signers.size(); j++){ + if(Pubkey::bytes_from_variant(signers[j]) == Pubkey::bytes_from_variant(array[i])){ + sign_at_index(j); + } + } + } + + check_fully_signed(); return OK; } diff --git a/tools/graphql_to_function.py b/tools/graphql_to_function.py new file mode 100644 index 00000000..23c92237 --- /dev/null +++ b/tools/graphql_to_function.py @@ -0,0 +1,509 @@ +import re +import argparse + + +CLASS_TYPE = "HoneyComb" +RETURN_TYPE = "Variant" + +SIGNER_TYPE = "const Variant&" +SERVER_SIGNER = 'Pubkey::new_from_string("11111111111111111111111111111111")' + +header_includes = [ + '"pubkey.hpp"', +] + +QL_TO_VARIANT = { + "String": "String", + "[String!]": "PackedStringArray", + "BigInt": "int64_t", + "Int": "int32_t", + "InitResourceInput": "Variant", + "TreeSetupConfig": "Variant", + "UserInfoInput": "Variant", +} + +GODOT_TYPE_DEFVAL = { + "String": '""', + "int64_t": '-1', + "int32_t": "-1", + "PackedStringArray": "PackedStringArray()", + "Variant": "Variant(nullptr)", +} + + +class GQLParse: + def __init__(self): + self.required_args = [] + self.optional_args = [] + self.original_required_args = [] + self.original_optional_args = [] + self.str = "" + self.method_definitions = "" + self.bound_methods = "" + self.method_implementations = "" + + def function_name_to_alias(self, function_name): + alias = function_name[6:-11] + return ''.join(['_'+c.lower() if c.isupper() else c for c in alias]).lstrip('_') + + + def ql_type_to_godot(self, ql_type): + return QL_TO_VARIANT[ql_type] + + + def add_required_arg_to_bind(self, arg): + self.bind_methods = "" + + + def add_required_arg(self, name, data_type): + self.required_args.append((name, data_type)) + + + def add_optional_arg(self, name, data_type): + self.optional_args.append((name, data_type)) + + + def process_arg(self, arg): + (name, data_type) = arg.split(":") + name = name.lstrip() + data_type = data_type.lstrip().rstrip() + assert(name[0] == '$') + name = name[1:] + if(data_type[-1] == "!"): + data_type = data_type[0:-1] + + self.add_required_arg_to_bind(arg) + self.add_required_arg(name, data_type) + + else: + self.add_optional_arg(name, data_type) + + + def read_args(self, str): + (function_name, str) = str.split("(", maxsplit=1) + function_name = function_name[0].lower() + function_name[1:] + + assert(str[-1] == ")") + str = str[0:-1] + + args = str.split(",") + for arg in args: + self.process_arg(arg) + + self.function_name = function_name + + + def print_arg(self, arg, optional=False): + result_string = "" + (arg_name, arg_type) = arg + godot_type = self.ql_type_to_godot(arg_type) + + is_optional = "false" + if optional: + is_optional = "true" + if arg_name in self.signers or arg_name in self.non_signers: + result_string += f'\tif({arg_name} != Variant(nullptr))' + "{\n\t" + else: + result_string += f'\tif({arg_name} != {GODOT_TYPE_DEFVAL[godot_type]})' + "{\n\t" + + result_string += "\tadd_arg" + + if godot_type == "String": + result_string += f'("{arg_name}", "{arg_type}", Pubkey::string_from_variant({arg_name}), {is_optional});\n' + elif godot_type == "Variant": + result_string += f'("{arg_name}", "{arg_type}", Object::cast_to<{arg_type}>({arg_name})->to_dict(), {is_optional});\n' + else: + result_string += f'("{arg_name}", "{arg_type}", {arg_name}, {is_optional});\n' + + if optional: + result_string += '\t}\n' + + return result_string + + def print_args_section(self): + + result_string = "" + + for required_arg in self.required_args: + result_string += self.print_arg(required_arg) + for optional_arg in self.optional_args: + result_string += self.print_arg(optional_arg, True) + + return result_string + + + def graphql_to_function(self, str, signers, non_signers): + self.str = str + self.signers = signers + self.non_signers = non_signers + self.required_args = [] + self.optional_args = [] + self.original_required_args = [] + self.original_optional_args = [] + + (firstWord, self.str) = self.str.split(maxsplit=1) + assert(firstWord == "query") + + func_header = re.match(r"(.*)\(([\s\S]*?)\)", self.str) + + self.read_args(func_header.group()) + + self.str = self.str[func_header.span()[1]:] + chunk_string = re.search(r"\{\s*[\w\s:,$]*?\([\s\S]*?\)", self.str) + self.str = self.str[chunk_string.span()[1]:] + + chunk_string = re.search(r"\{\s*tx\s*\{\s*[\w\s]*\}\s*[\w\s]*\}", self.str) + if not chunk_string: + chunk_string = re.search(r"\{\s*[\w\s]*\}", self.str) + + self.str = chunk_string.group() + self.str = self.str.lstrip()[1:] + self.str = self.str.rstrip()[0:-1] + + # Make a line of query + query_fields_re = re.sub(r"[\r\n]+", "", self.str) + self.query_fields = re.sub(r'\s+', ' ', query_fields_re) + + self.method_definitions += f'\t{self.print_function_name_header()}\n' + + self.method_implementations += self.print_function_name() + self.method_implementations += "{\n" + self.method_implementations += "\tif(pending){\n" + self.method_implementations += "\t\treturn ERR_BUSY;\n" + self.method_implementations += "\t}\n" + self.method_implementations += self.print_signer_section() + '\n' + self.method_implementations += self.print_args_section() + '\n' + self.method_implementations += self.print_method_name() + '\n' + self.method_implementations += self.print_last_section() + '\n' + + self.append_method_bind() + + + + self.str = self.str[chunk_string.span()[1]:] + return "" + + + def append_method_bind(self): + result_string = f'ClassDB::bind_method(D_METHOD("{self.function_name_to_alias(self.function_name)}", ' + + for required_arg in self.required_args: + (arg_name, _arg_type) = required_arg + result_string += f'"{arg_name}", ' + + for optional_arg in self.optional_args: + (arg_name, _arg_type) = optional_arg + result_string += f'"{arg_name}", ' + + if self.required_args or self.optional_args: + result_string = result_string[0:-2] + + result_string += f'), &{CLASS_TYPE}::{self.function_name}' + + if self.optional_args: + result_string += ', ' + + temp_optional_args = self.optional_args + #temp_optional_args.reverse() + + for optional_arg in temp_optional_args: + (_arg_name, arg_type) = optional_arg + godot_type = QL_TO_VARIANT[arg_type] + result_string += f'DEFVAL({GODOT_TYPE_DEFVAL[godot_type]}), ' + + if self.optional_args: + result_string = result_string[0:-2] + + result_string += ');\n' + + self.bound_methods += "\t" + result_string + + return result_string + + + def print_function_name(self): + result_string = "" + result_string += f"{RETURN_TYPE} {CLASS_TYPE}::{self.function_name}(" + + for required_arg in self.required_args: + result_string += f'{self.print_function_param(required_arg)}, ' + for optional_arg in self.optional_args: + result_string += f'{self.print_function_param(optional_arg)}, ' + + if self.required_args or self.optional_args: + result_string = result_string[0:-2] + + result_string += ")" + return result_string + + + def print_function_name_header(self): + result_string = "" + result_string += f"{RETURN_TYPE} {self.function_name}(" + + for required_arg in self.required_args: + result_string += f'{self.print_function_param(required_arg)}, ' + for optional_arg in self.optional_args: + (_arg_name, arg_type) = optional_arg + godot_type = QL_TO_VARIANT[arg_type] + result_string += f'{self.print_function_param(optional_arg)} = {GODOT_TYPE_DEFVAL[godot_type]}, ' + + if self.required_args or self.optional_args: + result_string = result_string[0:-2] + + result_string += ");" + return result_string + + def print_signer_section(self): + result_string = "" + for signer in self.signers: + if signer == ".": + result_string += f"\tsigners.append({SERVER_SIGNER});\n" + else: + result_string += f"\tsigners.append({signer});\n" + return result_string + + + def print_method_name(self): + result_string = f'\n\tmethod_name = "{self.function_name}";\n' + return result_string + + + def print_last_section(self): + result_string = f'\n\tquery_fields = "{self.query_fields}";\n' + result_string += "\tsend_query();\n" + result_string += "\treturn OK;\n" + result_string += "}\n" + + return result_string + + def print_function_param(self, arg): + (arg_name, arg_type) = arg + + if arg_name in self.signers or arg_name in self.non_signers: + godot_type = SIGNER_TYPE + else: + godot_type = self.ql_type_to_godot(arg_type) + + result_string = f"{godot_type} {arg_name}" + #print(result_string) + + return result_string + + + def print_bind_methods(self): + result_string = f"void {CLASS_TYPE}::_bind_methods()" + "{\n" + result_string += f'\tbind_non_changing_methods();\n' + result_string += self.bound_methods + result_string += "}" + return result_string + + + def print_header_includes(self): + result = "" + for header_include in header_includes: + result += f'#include {header_include}\n' + + return result + + def print_header_file(self): + result = self.print_header_includes() + result += "\nnamespace godot{\n" + result += f"\nClass {CLASS_TYPE} : public Node" + "{\n" + result += f"GDCLASS({CLASS_TYPE}, Node)\n" + result += "private:\n" + result += "protected:\n" + result += "static void _bind_methods();\n" + result += "public:\n" + result += self.method_definitions + result += "};\n" + result += "} // godot" + + return result + + + def print_cpp_file(self): + result = '#include "honeycomb.hpp"\n\n' + result += "namespace godot{\n\n" + result += self.method_implementations + result += parser.print_bind_methods() + '\n' + result += "} // godot" + + return result + + +CREATE_NEW_RESOURCE = """ +query CreateCreateNewResourceTransaction( + $project: String!, + $authority: String!, + $params: InitResourceInput!, + $delegateAuthority: String, + $payer: String, + $lutAddresses: [String!], + $computeUnitPrice: Int +) { + createCreateNewResourceTransaction( + project: $project, + authority: $authority, + params: $params, + delegateAuthority: $delegateAuthority, + payer: $payer, + lutAddresses: $lutAddresses, + computeUnitPrice: $computeUnitPrice + ) { + tx { + transaction + blockhash + lastValidBlockHeight + } + resource + } +} +""" + +CREATE_NEW_RESOURCE_TREE = """ +query CreateCreateNewResourceTreeTransaction( + $project: String!, + $resource: String!, + $authority: String!, + $treeConfig: TreeSetupConfig!, + $delegateAuthority: String, + $payer: String, + $lutAddresses: [String!], + $computeUnitPrice: Int +) { + createCreateNewResourceTreeTransaction( + project: $project, + resource: $resource, + authority: $authority, + treeConfig: $treeConfig, + delegateAuthority: $delegateAuthority, + payer: $payer, + lutAddresses: $lutAddresses, + computeUnitPrice: $computeUnitPrice + ) { + tx { + transaction + blockhash + lastValidBlockHeight + } + treeAddress + proofBytes + space + cost + maxTreeCapacity + } +} +""" + +MINT_RESOURCE_TRANSACTION = """ +query CreateMintResourceTransaction( + $resource: String!, + $owner: String!, + $authority: String!, + $amount: BigInt!, + $delegateAuthority: String, + $payer: String, + $lutAddresses: [String!], + $computeUnitPrice: Int +) { + createMintResourceTransaction( + resource: $resource, + owner: $owner, + authority: $authority, + amount: $amount, + delegateAuthority: $delegateAuthority, + payer: $payer, + lutAddresses: $lutAddresses, + computeUnitPrice: $computeUnitPrice + ) { + transaction + blockhash + lastValidBlockHeight + } +} +""" + +CREATE_BURN_RESOURCE_TRANSACTION = """ +query CreateBurnResourceTransaction( + $resource: String!, + $amount: BigInt!, + $authority: String!, + $owner: String, + $payer: String, + $delegateAuthority: String, + $lutAddresses: [String!], + $computeUnitPrice: Int +) { + createBurnResourceTransaction( + resource: $resource, + amount: $amount, + authority: $authority, + owner: $owner, + payer: $payer, + delegateAuthority: $delegateAuthority, + lutAddresses: $lutAddresses, + computeUnitPrice: $computeUnitPrice + ) { + transaction + blockhash + lastValidBlockHeight + } +} +""" + +CREATE_NEW_USER_TRANSACTION = """ +query CreateNewUserTransaction( + $info: UserInfoInput, + $wallet: String!, + $payer: String, + $lutAddresses: [String!], + $computeUnitPrice: Int +) { + createNewUserTransaction( + info: $info, + wallet: $wallet, + payer: $payer, + lutAddresses: $lutAddresses, + computeUnitPrice: $computeUnitPrice + ) { + transaction + blockhash + lastValidBlockHeight + } +} +""" + + +parser = GQLParse() +parser.graphql_to_function(CREATE_NEW_RESOURCE, ["authority", "."], ["project"]) +parser.graphql_to_function(CREATE_NEW_RESOURCE_TREE, ["authority", "delegateAuthority", "payer"], ["project", "resource"]) +parser.graphql_to_function(MINT_RESOURCE_TRANSACTION, ["owner", "authority", "delegateAuthority", "payer"], ["resource"]) +parser.graphql_to_function(CREATE_BURN_RESOURCE_TRANSACTION, ["authority", "owner", "payer", "delegateAuthority"], ["resource"]) +parser.graphql_to_function(CREATE_NEW_USER_TRANSACTION, ["wallet", "payer"], []) + +#print(parser.print_header_file()) +#print(parser.print_cpp_file()) + + +def main(): + parser = argparse.ArgumentParser(description='Generates HoneyComb interface source code.') + parser.add_argument('-o', '--out_directory', metavar="PATH", type=str, default="src/HoneyComb/generated", + help='Output directory of generated source files.') + parser.add_argument('-c', '--cpp_filename', type=str, default="honeycomb_generated.cpp", + help='File name of generated cpp file.') + parser.add_argument('-p', '--hpp_filename', type=str, default="honeycomb_generated.hpp", + help='File name of generated hpp file.') + parser.add_argument('--skip_hpp_file', action="store_true", default=False, + help='Tells generator to skip creating a hpp file.') + parser.add_argument('--skip_cpp_file', action="store_true", default=False, + help='Tells generator to skip creating a cpp file.') + parser.add_argument('--skip_resources', action="store_true", default=False, + help='Tells generator to skip creating resource classes.') + + args = parser.parse_args() + print(args.out_directory) + +if __name__ == "__main__": + main() +