├── .gitignore ├── Makefile ├── README.md ├── include ├── abieos_numeric.hpp └── smart_account_creator.hpp ├── smart_account_creator.abi └── smart_account_creator.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.wast 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CPP_IN=smart_account_creator 2 | 3 | build: 4 | eosiocpp -o $(CPP_IN).wast $(CPP_IN).cpp 5 | 6 | abi: 7 | eosiocpp -g $(CPP_IN).abi $(CPP_IN).cpp 8 | 9 | all: build abi 10 | 11 | clean: 12 | rm -f $(CPP_IN).wast $(CPP_IN).wasm 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Account Creator for EOS 2 | 3 | This is a smart contract for EOS specifically designed for people who have their EOS 4 | on an exchange and don't have their own EOS account yet. It removes the need for a third-party like a friend or 5 | an account creation service. Since it's a smart contract, the account creation happens instantly, automatically and trustless. 6 | 7 | This contract has been forked and modified from the original to suit the purposes of Greymass tools, and to enable maintenence and compatibility. 8 | 9 | ## How to use? 10 | Send the recommended EOS to the contract which is deployed at the EOS account ```setupaccount```. In the memo, 11 | you give the desired account name, the owner public key and the active public key separated by the ```-``` character. 12 | 13 | For example, if your account name is ```mynewaccount```, your owner key is ```EOS6ra2QHsDr6yMyFaPaNwe3Hz8XmYRj3B68e5tbDchyPTTasgFH9``` 14 | and your active key is ```EOS8WcL1CroNrXfdphkohCmea1Jgp7TpqQXrkpcF1gETweeSnphmJ```, the memo string would be: 15 | 16 | ``` 17 | mynewaccount-EOS6ra2QHsDr6yMyFaPaNwe3Hz8XmYRj3B68e5tbDchyPTTasgFH9-EOS8WcL1CroNrXfdphkohCmea1Jgp7TpqQXrkpcF1gETweeSnphmJ 18 | ``` 19 | 20 | Some exchanges like Binance don't allow to use a long memo. In this case or if you use the same key for owner and active, the second key including the ```-``` separator can be omitted. 21 | So that would be a valid memo string as well: 22 | 23 | ``` 24 | mynewaccount-EOS6ra2QHsDr6yMyFaPaNwe3Hz8XmYRj3B68e5tbDchyPTTasgFH9 25 | ``` 26 | 27 | ## How does it work? 28 | When you withdraw your EOS to the setupaccount smart contract, it will perform the following steps in order: 29 | 30 | 1. Create a new account using your specified name, owner key and active key 31 | 1. Buy 3 KB of RAM for your new account with parts of the transferred EOS. Every account that is created on the EOS network needs RAM to exist. 32 | 1. Delegate and transfer 0.1 EOS for CPU and 0.1 EOS for NET. 33 | 1. Forward the remaining EOS balance to your new account. 34 | 35 | Should any of the above actions fail, the transaction will be rolled back which 36 | means the money will automatically be refunded to you. 37 | -------------------------------------------------------------------------------- /include/abieos_numeric.hpp: -------------------------------------------------------------------------------- 1 | // copyright defined in abieos/LICENSE.txt 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | namespace abieos { 13 | 14 | const char base58_chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; 15 | 16 | bool map_initialized = false; 17 | std::array base58_map{{0}}; 18 | auto get_base58_map() { 19 | if(!map_initialized) { 20 | for (unsigned i = 0; i < base58_map.size(); ++i) 21 | base58_map[i] = -1; 22 | for (unsigned i = 0; i < sizeof(base58_chars); ++i) 23 | base58_map[base58_chars[i]] = i; 24 | map_initialized = true; 25 | } 26 | return base58_map; 27 | } 28 | 29 | 30 | template 31 | std::array base58_to_binary(std::string_view s) { 32 | std::array result{{0}}; 33 | for (auto& src_digit : s) { 34 | int carry = get_base58_map()[src_digit]; 35 | if (carry < 0) 36 | eosio_assert(0, "invalid base-58 value"); 37 | for (auto& result_byte : result) { 38 | int x = result_byte * 58 + carry; 39 | result_byte = x; 40 | carry = x >> 8; 41 | } 42 | if (carry) 43 | eosio_assert(0, "base-58 value is out of range"); 44 | } 45 | std::reverse(result.begin(), result.end()); 46 | return result; 47 | } 48 | 49 | 50 | enum class key_type : uint8_t { 51 | k1 = 0, 52 | r1 = 1, 53 | }; 54 | 55 | 56 | template 57 | Key string_to_key(std::string_view s, key_type type, const char (&suffix)[suffix_size]) { 58 | static const auto size = std::tuple_size::value; 59 | auto whole = base58_to_binary(s); 60 | Key result{(uint8_t)type}; 61 | memcpy(result.data.data(), whole.data(), result.data.size()); 62 | return result; 63 | } 64 | 65 | 66 | eosio::public_key string_to_public_key(std::string_view s) { 67 | if (s.size() >= 3 && s.substr(0, 3) == "EOS" ) { 68 | auto whole = base58_to_binary<37>(s.substr(3)); 69 | eosio::public_key key{(uint8_t)key_type::k1}; 70 | static_assert(whole.size() == key.data.size() + 4, "Error: whole.size() != key.data.size() + 4"); 71 | memcpy(key.data.data(), whole.data(), key.data.size()); 72 | return key; 73 | } else if (s.size() >= 7 && s.substr(0, 7) == "PUB_R1_") { 74 | return string_to_key(s.substr(7), key_type::r1, "R1"); // 75 | } else { 76 | eosio_assert(0, "unrecognized public key format"); 77 | } 78 | } 79 | 80 | } // namespace abieos 81 | -------------------------------------------------------------------------------- /include/smart_account_creator.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "abieos_numeric.hpp" 15 | 16 | 17 | namespace eosio { 18 | 19 | // Temporary authority until native is fixed. Ref: https://github.com/EOSIO/eos/issues/4669 20 | struct wait_weight { 21 | uint32_t wait_sec; 22 | weight_type weight; 23 | }; 24 | struct authority { 25 | uint32_t threshold; 26 | vector keys; 27 | vector accounts; 28 | vector waits; 29 | }; 30 | 31 | 32 | // eosiosystem::native::newaccount Doesn't seem to want to take authorities. 33 | struct call { 34 | struct eosio { 35 | void newaccount(account_name creator, account_name name, 36 | authority owner, authority active); 37 | }; 38 | }; 39 | 40 | 41 | asset rambytes_price(uint32_t bytes) { 42 | eosiosystem::rammarket market(N(eosio), N(eosio)); 43 | auto itr = market.find(S(4, RAMCORE)); 44 | eosio_assert(itr != market.end(), "RAMCORE market not found"); 45 | auto tmp = *itr; 46 | return tmp.convert(asset(bytes, S(0, RAM)), CORE_SYMBOL); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /smart_account_creator.abi: -------------------------------------------------------------------------------- 1 | { 2 | "____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-07-06T18:14:49", 3 | "version": "eosio::abi/1.0", 4 | "types": [], 5 | "structs": [{ 6 | "name": "transfer", 7 | "base": "", 8 | "fields": [{ 9 | "name": "sender", 10 | "type": "name" 11 | },{ 12 | "name": "receiver", 13 | "type": "name" 14 | } 15 | ] 16 | } 17 | ], 18 | "actions": [{ 19 | "name": "transfer", 20 | "type": "transfer", 21 | "ricardian_contract": "" 22 | } 23 | ], 24 | "tables": [], 25 | "ricardian_clauses": [], 26 | "error_messages": [], 27 | "abi_extensions": [] 28 | } -------------------------------------------------------------------------------- /smart_account_creator.cpp: -------------------------------------------------------------------------------- 1 | #include "include/smart_account_creator.hpp" 2 | 3 | using namespace eosio; 4 | 5 | class sac : public contract { 6 | public: 7 | sac(account_name self) : eosio::contract(self) {} 8 | 9 | 10 | void transfer(const account_name sender, const account_name receiver) { 11 | const auto transfer = unpack_action_data(); 12 | if (transfer.from == _self || transfer.to != _self) { 13 | // This is an outgoing transfer, do nothing. 14 | return; 15 | } 16 | 17 | // Parse Memo: Memo must have format "account_name-owner_key(-active_key)" 18 | eosio_assert(transfer.quantity.symbol == CORE_SYMBOL, "Must be CORE_SYMBOL"); 19 | eosio_assert(transfer.quantity.is_valid(), "Invalid token transfer"); 20 | eosio_assert(transfer.quantity.amount > 0, "Quantity must be positive"); 21 | 22 | eosio_assert(transfer.memo.length() == 120 || transfer.memo.length() == 66, "Malformed Memo (not right length)"); 23 | const string account_string = transfer.memo.substr(0, 12); 24 | const account_name account_to_create = string_to_name(account_string.c_str()); 25 | eosio_assert(transfer.memo[12] == ':' || transfer.memo[12] == '-', "Malformed Memo [12] == : or -"); 26 | 27 | const string owner_key_str = transfer.memo.substr(13, 53); 28 | string active_key_str; 29 | if(transfer.memo[66] == ':' || transfer.memo[66] == '-') { 30 | active_key_str = transfer.memo.substr(67, 53); // Active key provided. 31 | } else { 32 | active_key_str = owner_key_str; // Active key becomes the same as owner. 33 | } 34 | 35 | const auto owner_pubkey = abieos::string_to_public_key(owner_key_str); 36 | const auto active_pubkey = abieos::string_to_public_key(active_key_str); 37 | 38 | const auto owner_auth = authority{ 1, {{owner_pubkey, 1}}, {}, {} }; 39 | const auto active_auth = authority{ 1, {{active_pubkey, 1}}, {}, {} }; 40 | 41 | const auto amount = rambytes_price(3 * 1024); 42 | const auto ram_replace = rambytes_price(256); 43 | const auto cpu = asset(1000); 44 | const auto net = asset(1000); 45 | 46 | const auto remaining_balance = transfer.quantity - cpu - net - amount - ram_replace; 47 | 48 | eosio_assert(remaining_balance.amount >= 0, "Not enough money"); 49 | 50 | // Create account. 51 | INLINE_ACTION_SENDER(call::eosio, newaccount) 52 | (N(eosio), {{_self, N(active)}}, 53 | {_self, account_to_create, owner_auth, active_auth}); 54 | 55 | // Buy ram for account. 56 | INLINE_ACTION_SENDER(eosiosystem::system_contract, buyram) 57 | (N(eosio), {{_self, N(active)}}, 58 | {_self, account_to_create, amount}); 59 | 60 | // Replace lost ram. 61 | INLINE_ACTION_SENDER(eosiosystem::system_contract, buyram) 62 | (N(eosio), {{_self, N(active)}}, 63 | {_self, _self, ram_replace}); 64 | 65 | // Delegate and transfer cpu and net. 66 | INLINE_ACTION_SENDER(eosiosystem::system_contract, delegatebw) 67 | (N(eosio), {{_self, N(active)}}, 68 | {_self, account_to_create, net, cpu, 1}); 69 | 70 | if (remaining_balance.amount > 0) { 71 | // Transfer remaining balance to new account. 72 | INLINE_ACTION_SENDER(eosio::token, transfer) 73 | (N(eosio.token), {{_self, N(active)}}, 74 | {_self, account_to_create, remaining_balance, std::string("Initial balance")}); 75 | } 76 | } 77 | }; 78 | 79 | 80 | #define EOSIO_ABI_EX(TYPE, MEMBERS) \ 81 | extern "C" { \ 82 | void apply(uint64_t receiver, uint64_t code, uint64_t action) { \ 83 | if (action == N(onerror)) { \ 84 | /* onerror is only valid if it is for the "eosio" code account and \ 85 | * authorized by "eosio"'s "active permission */ \ 86 | eosio_assert(code == N(eosio), "onerror action's are only valid from " \ 87 | "the \"eosio\" system account"); \ 88 | } \ 89 | auto self = receiver; \ 90 | if (code == self || code == N(eosio.token) || action == N(onerror)) { \ 91 | TYPE thiscontract(self); \ 92 | switch (action) { EOSIO_API(TYPE, MEMBERS) } \ 93 | /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \ 94 | } \ 95 | } \ 96 | } 97 | 98 | EOSIO_ABI_EX(sac, (transfer)) 99 | --------------------------------------------------------------------------------