├── .gitignore ├── Makefile ├── README.md ├── exchange_state.cpp ├── exchange_state.hpp ├── gen.py ├── public_key.hpp ├── sac.cpp └── sac.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.wast 3 | /sac.abi 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CPP_IN=sac 2 | CONTRACT_ACCOUNT=accountcreat 3 | # CONTRACT_ACCOUNT=fwwenbnhyljq 4 | PK=EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV 5 | EOS_CONTRACTS_DIR=/Users/al/Projects/eos/eosio.contracts/build/contracts 6 | ACCOUNT_NAME := $(shell python gen.py) 7 | CLEOS=cleos 8 | # CLEOS=cleos -u https://eos.greymass.com 9 | # CLEOS=cleos -u https://bos-api.eosauthority.com 10 | 11 | # CLEOS=cleos -u https://eos.greymass.com 12 | NONCE := $(shell openssl rand 8 -hex) 13 | HASH := $(shell /bin/echo -n '$(ACCOUNT_NAME)$(NONCE)'| shasum -a256 | awk '{print $$1}') 14 | 15 | 16 | build: 17 | eosio-cpp -I. -abigen $(CPP_IN).cpp -o $(CPP_IN).wasm 18 | 19 | deploy: build 20 | $(CLEOS) set contract $(CONTRACT_ACCOUNT) . $(CPP_IN).wasm $(CPP_IN).abi 21 | 22 | system: 23 | $(CLEOS) create account eosio eosio.rex $(PK) $(PK) 24 | $(CLEOS) create account eosio eosio.token $(PK) $(PK) 25 | $(CLEOS) create account eosio eosio.msig $(PK) $(PK) 26 | $(CLEOS) create account eosio eosio.bpay $(PK) $(PK) 27 | $(CLEOS) create account eosio eosio.names $(PK) $(PK) 28 | $(CLEOS) create account eosio eosio.ram $(PK) $(PK) 29 | $(CLEOS) create account eosio eosio.ramfee $(PK) $(PK) 30 | $(CLEOS) create account eosio eosio.saving $(PK) $(PK) 31 | $(CLEOS) create account eosio eosio.stake $(PK) $(PK) 32 | $(CLEOS) create account eosio eosio.vpay $(PK) $(PK) 33 | $(CLEOS) set contract eosio.token $(EOS_CONTRACTS_DIR)/eosio.token -p eosio.token 34 | $(CLEOS) set contract eosio.msig $(EOS_CONTRACTS_DIR)/eosio.msig -p eosio.msig 35 | $(CLEOS) push action eosio.token create '["eosio", "10000000000.0000 EOS",0,0,0]' -p eosio.token 36 | $(CLEOS) push action eosio.token issue '["eosio","1000000000.0000 EOS", "issue"]' -p eosio 37 | $(CLEOS) set contract eosio $(EOS_CONTRACTS_DIR)/eosio.system -p eosio 38 | $(CLEOS) push action eosio init '[0, "4,EOS"]' -p eosio 39 | 40 | setup: 41 | $(CLEOS) system newaccount --stake-net "1.0000 EOS" --stake-cpu "1.0000 EOS" --buy-ram-kbytes 8000 eosio $(CONTRACT_ACCOUNT) $(PK) $(PK) 42 | $(CLEOS) set account permission $(CONTRACT_ACCOUNT) active '{"threshold": 1,"keys": [{"key": "$(PK)","weight": 1}],"accounts": [{"permission":{"actor":"$(CONTRACT_ACCOUNT)","permission":"eosio.code"},"weight":1}]}' owner -p $(CONTRACT_ACCOUNT) 43 | $(CLEOS) system newaccount --stake-net "1.0000 EOS" --stake-cpu "1.0000 EOS" --buy-ram-kbytes 8000 eosio saccountfees $(PK) $(PK) 44 | $(CLEOS) system newaccount --stake-net "1.0000 EOS" --stake-cpu "1.0000 EOS" --buy-ram-kbytes 8000 eosio angelo $(PK) $(PK) 45 | $(CLEOS) transfer eosio angelo "1000.0000 EOS" 46 | $(CLEOS) transfer eosio accountcreat "1000.0000 EOS" 47 | 48 | 49 | test: 50 | $(CLEOS) transfer angelo $(CONTRACT_ACCOUNT) "10.0000 EOS" "$(ACCOUNT_NAME):EOS7R6HoUvevAtoLqUMSix74x9Wk4ig75tA538HaGXLFKpquKCPkH:EOS6bWFTECWtssKrHQVrkKKf68EydHNyr1ujv23KCZMFUxqwcGqC3" -p $(CONTRACT_ACCOUNT)@active -p angelo@active 51 | $(CLEOS) get account $(ACCOUNT_NAME) 52 | 53 | testbinance: 54 | $(CLEOS) push action $(CONTRACT_ACCOUNT) regaccount '["angelo", "$(HASH)", "$(PK)", "$(PK)"]' -p angelo 55 | $(CLEOS) transfer angelo $(CONTRACT_ACCOUNT) "1.0000 EOS" "$(ACCOUNT_NAME)$(NONCE)" -p angelo 56 | $(CLEOS) get account $(ACCOUNT_NAME) 57 | 58 | clearexpired: 59 | $(CLEOS) push action $(CONTRACT_ACCOUNT) clearexpired '["angelo"]' -p angelo 60 | 61 | show: 62 | $(CLEOS) get table $(CONTRACT_ACCOUNT) $(CONTRACT_ACCOUNT) order 63 | 64 | clean: 65 | rm -f $(CPP_IN).wast $(CPP_IN).wasm -------------------------------------------------------------------------------- /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 | ## How to use? 8 | There are two ways this smart contract can be used. Method 1 is the easiest since the memo can be constructed without the help of software. The disadvantage of Method 1 is that due to the length of the resulting memo, it does not work with some exchanges such as Binance or Huobi. Method 2 requires code to register the order with the smart contract as well as to generate the memo, but it's the only method that works with those exchanges. 9 | 10 | ### Method 1 (does *not* work with Binance or Huobi) 11 | Send at least 3 EOS to the contract which is deployed at the EOS account ```accountcreat```. In the memo, 12 | you give the desired account name, the owner public key and the active public key separated by the ```-``` character. 13 | 14 | For example, if your account name is ```mynewaccount```, your owner key is ```EOS6ra2QHsDr6yMyFaPaNwe3Hz8XmYRj3B68e5tbDchyPTTasgFH9``` 15 | and your active key is ```EOS8WcL1CroNrXfdphkohCmea1Jgp7TpqQXrkpcF1gETweeSnphmJ```, the memo string would be: 16 | 17 | ``` 18 | mynewaccount-EOS6ra2QHsDr6yMyFaPaNwe3Hz8XmYRj3B68e5tbDchyPTTasgFH9-EOS8WcL1CroNrXfdphkohCmea1Jgp7TpqQXrkpcF1gETweeSnphmJ 19 | ``` 20 | 21 | 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. 22 | So that would be a valid memo string as well: 23 | 24 | ``` 25 | mynewaccount-EOS6ra2QHsDr6yMyFaPaNwe3Hz8XmYRj3B68e5tbDchyPTTasgFH9 26 | ``` 27 | If you need help, visit the [EOS Account Creator Website](https://eos-account-creator.com/eos/). It will assist you in generating they keys and building the correct memo string. 28 | 29 | ### Method 2 (*does* work with Binance and Huobi) 30 | This method uses a commit-reveal scheme to register the owner and active public keys with the smart contract. 31 | #### Registering your Public Keys with the smart contract 32 | Let ACCOUNT_NAME be your 12 character EOS account name that you want to register. Generate a nonce like this: 33 | ``` 34 | openssl rand 8 -hex 35 | ``` 36 | 37 | Generate a sha256 hash like this, by appending the nonce you just created to your account name: 38 | ``` 39 | echo -n '$(ACCOUNT_NAME)$(NONCE)'| shasum -a256 | awk '{print $1}' 40 | ``` 41 | Now we're ready to register our account creation order with the smart contract like this: 42 | 43 | ``` 44 | cleos push action $(CONTRACT_ACCOUNT) regaccount '["$(SENDER)", "$(HASH)", "$(OWNER_PK)", "$(ACTIVE_PK)"]' 45 | ``` 46 | The SENDER account will pay for the RAM required to store this order in the smart contract table. After successful account creation, this RAM will be released. If the account is not created, the RAM can be released after 3 hours by sending the action "clearexpired" to the contract. 47 | 48 | We can now finally register the account by making an EOS token transfer to the CONTRACT_ACCOUNT like this: 49 | ``` 50 | cleos transfer $(SENDER) $(CONTRACT_ACCOUNT) "1.0000 EOS" '$(ACCOUNT_NAME)$(NONCE)' 51 | ``` 52 | 53 | ## How does it work? 54 | When you withdraw your EOS to the accountcreat smart contract, it will perform the following steps in order: 55 | 56 | 1. Create a new account using your specified name, owner key and active key 57 | 1. Buy 4KB of RAM for your new account with parts of the transferred EOS. Every account that is created on the EOS network needs 4 KB of RAM to exist. 58 | 1. Delegate and transfer 0.1 EOS for CPU and 0.1 EOS for NET. 59 | 1. Deduct our fee of 0.5% or a minimum of 0.1 EOS and forward the remaining EOS balance to your new account. 60 | 61 | Should any of the above actions fail, the transaction will be rolled back which 62 | means the money will automatically be refunded to you. 63 | 64 | If you want to use this code or need help modifying it to your needs, please contact me at: hello@eos-account-creator.com 65 | 66 | ## Peer reviews 67 | If you want to review the code, that would be great. It's important for the community to have peer-reviewed, trusted, account creation smart contract code. Quick note to anyone wanting to verify the deployment (as there is no such things as etherscan yet, where you can upload the code): The code currently deployed on ```accountcreat``` is compiled with ```-Oz``` to save RAM. 68 | -------------------------------------------------------------------------------- /exchange_state.cpp: -------------------------------------------------------------------------------- 1 | #include "exchange_state.hpp" 2 | 3 | namespace eosiosystem { 4 | asset exchange_state::convert_to_exchange( connector& c, asset in ) { 5 | 6 | real_type R(supply.amount); 7 | real_type C(c.balance.amount+in.amount); 8 | real_type F(c.weight); 9 | real_type T(in.amount); 10 | real_type ONE(1.0); 11 | 12 | real_type E = -R * (ONE - std::pow( ONE + T / C, F) ); 13 | int64_t issued = int64_t(E); 14 | 15 | supply.amount += issued; 16 | c.balance.amount += in.amount; 17 | 18 | return asset( issued, supply.symbol ); 19 | } 20 | 21 | asset exchange_state::convert_from_exchange( connector& c, asset in ) { 22 | check( in.symbol== supply.symbol, "unexpected asset symbol input" ); 23 | 24 | real_type R(supply.amount - in.amount); 25 | real_type C(c.balance.amount); 26 | real_type F(1.0/c.weight); 27 | real_type E(in.amount); 28 | real_type ONE(1.0); 29 | 30 | 31 | // potentially more accurate: 32 | // The functions std::expm1 and std::log1p are useful for financial calculations, for example, 33 | // when calculating small daily interest rates: (1+x)n 34 | // -1 can be expressed as std::expm1(n * std::log1p(x)). 35 | // real_type T = C * std::expm1( F * std::log1p(E/R) ); 36 | 37 | real_type T = C * (std::pow( ONE + E/R, F) - ONE); 38 | int64_t out = int64_t(T); 39 | 40 | supply.amount -= in.amount; 41 | c.balance.amount -= out; 42 | 43 | return asset( out, c.balance.symbol ); 44 | } 45 | 46 | asset exchange_state::convert( asset from, const symbol& to ) { 47 | auto sell_symbol = from.symbol; 48 | auto ex_symbol = supply.symbol; 49 | auto base_symbol = base.balance.symbol; 50 | auto quote_symbol = quote.balance.symbol; 51 | 52 | //print( "From: ", from, " TO ", asset( 0,to), "\n" ); 53 | //print( "base: ", base_symbol, "\n" ); 54 | //print( "quote: ", quote_symbol, "\n" ); 55 | //print( "ex: ", supply.symbol, "\n" ); 56 | 57 | if( sell_symbol != ex_symbol ) { 58 | if( sell_symbol == base_symbol ) { 59 | from = convert_to_exchange( base, from ); 60 | } else if( sell_symbol == quote_symbol ) { 61 | from = convert_to_exchange( quote, from ); 62 | } else { 63 | check( false, "invalid sell" ); 64 | } 65 | } else { 66 | if( to == base_symbol ) { 67 | from = convert_from_exchange( base, from ); 68 | } else if( to == quote_symbol ) { 69 | from = convert_from_exchange( quote, from ); 70 | } else { 71 | check( false, "invalid conversion" ); 72 | } 73 | } 74 | 75 | if( to != from.symbol ) 76 | return convert( from, to ); 77 | 78 | return from; 79 | } 80 | 81 | 82 | 83 | } /// namespace eosiosystem -------------------------------------------------------------------------------- /exchange_state.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace eosiosystem { 7 | using eosio::asset; 8 | using eosio::symbol; 9 | 10 | typedef double real_type; 11 | 12 | /** 13 | * Uses Bancor math to create a 50/50 relay between two asset types. The state of the 14 | * bancor exchange is entirely contained within this struct. There are no external 15 | * side effects associated with using this API. 16 | */ 17 | struct [[eosio::table, eosio::contract("eosio.system")]] exchange_state { 18 | asset supply; 19 | 20 | struct connector { 21 | asset balance; 22 | double weight = .5; 23 | 24 | EOSLIB_SERIALIZE( connector, (balance)(weight) ) 25 | }; 26 | 27 | connector base; 28 | connector quote; 29 | 30 | uint64_t primary_key()const { return supply.symbol.raw(); } 31 | 32 | asset convert_to_exchange( connector& c, asset in ); 33 | asset convert_from_exchange( connector& c, asset in ); 34 | asset convert( asset from, const symbol& to ); 35 | 36 | EOSLIB_SERIALIZE( exchange_state, (supply)(base)(quote) ) 37 | }; 38 | 39 | typedef eosio::multi_index< "rammarket"_n, exchange_state > rammarket; 40 | 41 | } /// namespace eosiosystem -------------------------------------------------------------------------------- /gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import random 4 | 5 | alphabet = "12345abcdefghijklmnoprstuvwxyz" 6 | 7 | print(''.join(random.Random().choices(alphabet, weights=None, k=12))) 8 | -------------------------------------------------------------------------------- /public_key.hpp: -------------------------------------------------------------------------------- 1 | // copyright defined in abieos/LICENSE.txt 2 | // Adapted from https://github.com/EOSIO/abieos/blob/master/src/abieos_numeric.hpp 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace eosio; 13 | namespace abieos { 14 | 15 | const char base58_chars[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; 16 | 17 | bool map_initialized = false; 18 | std::array base58_map{{0}}; 19 | auto get_base58_map() { 20 | if(!map_initialized) { 21 | for (unsigned i = 0; i < base58_map.size(); ++i) 22 | base58_map[i] = -1; 23 | for (unsigned i = 0; i < sizeof(base58_chars); ++i) 24 | base58_map[base58_chars[i]] = i; 25 | map_initialized = true; 26 | } 27 | return base58_map; 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 | check(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 | check(0, "base-58 value is out of range"); 44 | } 45 | std::reverse(result.begin(), result.end()); 46 | return result; 47 | } 48 | 49 | enum class key_type : uint8_t { 50 | k1 = 0, 51 | r1 = 1, 52 | }; 53 | 54 | 55 | template 56 | Key string_to_key(std::string_view s, key_type type, const char (&suffix)[suffix_size]) { 57 | static const auto size = std::tuple_size::value; 58 | auto whole = base58_to_binary(s); 59 | Key result{static_cast(type)}; 60 | memcpy(result.data.data(), whole.data(), result.data.size()); 61 | return result; 62 | } 63 | 64 | 65 | eosio::public_key string_to_public_key(std::string_view s) { 66 | if (s.size() >= 3 && s.substr(0, 3) == "EOS") { 67 | auto whole = base58_to_binary<37>(s.substr(3)); 68 | eosio::public_key key{static_cast(key_type::k1)}; 69 | check(whole.size() == key.data.size() + 4, "Error: whole.size() != key.data.size() + 4"); 70 | memcpy(key.data.data(), whole.data(), key.data.size()); 71 | return key; 72 | } else if (s.size() >= 7 && s.substr(0, 7) == "PUB_R1_") { 73 | return string_to_key(s.substr(7), key_type::r1, "R1"); 74 | } else { 75 | check(0, "unrecognized public key format"); 76 | return eosio::public_key{}; // this is never returned, but shuts up compiler warnings 77 | } 78 | } 79 | 80 | } // namespace abieos 81 | -------------------------------------------------------------------------------- /sac.cpp: -------------------------------------------------------------------------------- 1 | #include "sac.hpp" 2 | #include "exchange_state.cpp" 3 | 4 | ACTION sac::regaccount(const name sender, const checksum256 hash, const eosio::public_key owner_key, const eosio::public_key active_key) { 5 | require_auth(sender); 6 | 7 | do_clearexpired(); 8 | 9 | auto idx = orders.template get_index<"bykey"_n>(); 10 | auto itr = idx.find(hash); 11 | if(itr != idx.end()) { 12 | // fail gracefully if it already exists 13 | return; 14 | } 15 | 16 | orders.emplace(sender, [&](auto& order) { 17 | order.id = orders.available_primary_key(); 18 | order.expires_at = now() + EXPIRE_TIMEOUT; 19 | order.hash = hash; 20 | order.owner_key = owner_key; 21 | order.active_key = active_key; 22 | }); 23 | }; 24 | 25 | ACTION sac::clearexpired(const name sender) { 26 | // if user orders and account, but never creates it, we need a way to reclaim that RAM 27 | // can be called by anyone by design 28 | do_clearexpired(); 29 | }; 30 | 31 | [[eosio::on_notify("eosio.token::transfer")]] 32 | void sac::transfer(const name from, const name to, const asset quantity, const std::string memo) { 33 | print("Ohai transfer!"); 34 | // only respond to incoming transfers 35 | if (from == _self || to != _self) { 36 | return; 37 | } 38 | 39 | // don't do anything on transfers from our reference account 40 | if (from == "ge4dknjtgqge"_n || from == "eosio.ram"_n) { 41 | return; 42 | } 43 | 44 | // only handle EOS transfers, ignore anything else 45 | if(quantity.symbol != core_symbol) { 46 | return; 47 | } 48 | 49 | check(quantity.is_valid(), "Are you trying to corrupt me?"); 50 | check(quantity.amount > 0, "Amount must be > 0"); 51 | 52 | // check if memo contains order 53 | const auto hash = sha256(trim(memo).c_str(), memo.length()); 54 | auto idx = orders.template get_index<"bykey"_n>(); 55 | auto itr = idx.find(hash); 56 | 57 | struct account_t data; 58 | if(itr != idx.end()) { 59 | 60 | data.name = name(memo.substr(0, 12)); 61 | 62 | data.owner_key = itr->owner_key; 63 | data.active_key = itr->active_key; 64 | data.stake_cpu = default_cpu_stake; 65 | data.ram_amount_bytes = default_ram_amount_bytes; 66 | idx.erase(itr); 67 | } else { 68 | parse_memo(memo, data); 69 | } 70 | create_account(quantity, data); 71 | } 72 | 73 | -------------------------------------------------------------------------------- /sac.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "exchange_state.hpp" 5 | #include "public_key.hpp" 6 | #include 7 | 8 | using namespace eosio; 9 | using namespace std; 10 | CONTRACT sac : public contract { 11 | public: 12 | using contract::contract; 13 | sac(name self, name code, datastream ds) : 14 | eosio::contract(self,code,ds), 15 | orders(_self, _self.value) 16 | {} 17 | 18 | const uint32_t EXPIRE_TIMEOUT = 60*60*3; 19 | 20 | static constexpr symbol core_symbol{"EOS", 4}; 21 | static constexpr name system_account{"eosio"}; 22 | static constexpr name eosio_token_account{"eosio.token"}; 23 | static constexpr symbol RAM_symbol{"RAM", 0}; 24 | static constexpr symbol RAMCORE_symbol{"RAMCORE", 4}; 25 | 26 | const asset default_cpu_stake{1500, core_symbol}; 27 | const asset default_net_stake{500, core_symbol}; 28 | const uint32_t default_ram_amount_bytes{3000}; 29 | 30 | struct permission_level_weight { 31 | permission_level permission; 32 | uint16_t weight; 33 | }; 34 | struct key_weight { 35 | eosio::public_key key; 36 | uint16_t weight; 37 | }; 38 | struct wait_weight { 39 | uint32_t wait_sec; 40 | uint16_t weight; 41 | }; 42 | struct authority { 43 | uint32_t threshold; 44 | std::vector keys; 45 | std::vector accounts; 46 | std::vector waits; 47 | }; 48 | struct newaccount { 49 | name creator; 50 | name name; 51 | authority owner; 52 | authority active; 53 | }; 54 | 55 | struct account_t { 56 | name name; 57 | eosio::public_key owner_key; 58 | eosio::public_key active_key; 59 | asset stake_cpu; 60 | uint32_t ram_amount_bytes; 61 | }; 62 | 63 | 64 | TABLE order { 65 | uint64_t id; 66 | uint32_t expires_at; 67 | checksum256 hash; 68 | eosio::public_key owner_key; 69 | eosio::public_key active_key; 70 | 71 | uint64_t primary_key()const { return id; } 72 | 73 | checksum256 by_checksum()const { return hash; } 74 | uint64_t get_expires_at()const { return (uint64_t)expires_at; } 75 | }; 76 | typedef eosio::multi_index< name("order"), order, 77 | indexed_by< "bykey"_n, const_mem_fun >, 78 | indexed_by< "expiresat"_n, const_mem_fun > 79 | > order_index; 80 | order_index orders; 81 | 82 | 83 | ACTION regaccount(const name sender, const checksum256 hash, const eosio::public_key owner_key, const eosio::public_key active_key); 84 | ACTION clearexpired(const name sender); 85 | void transfer(const name from, const name to, const asset quantity, const std::string memo); 86 | 87 | private: 88 | static uint32_t now() { 89 | return current_time_point().sec_since_epoch(); 90 | } 91 | 92 | void do_clearexpired() { 93 | std::vector l; 94 | auto idx = orders.template get_index<"expiresat"_n>(); 95 | 96 | // idx is now sorted by expires_at, oldest first 97 | for( const auto& item : idx ) { 98 | if(item.expires_at < now()) { 99 | l.push_back(item); 100 | } else { 101 | break; 102 | } 103 | } 104 | 105 | // delete in second pass 106 | for (order item : l) { 107 | orders.erase(orders.find(item.primary_key())); 108 | } 109 | } 110 | 111 | asset determine_ram_price(uint32_t bytes) { 112 | eosiosystem::rammarket rammarkettable(system_account, system_account.value); 113 | auto market = rammarkettable.get(RAMCORE_symbol.raw()); 114 | auto ram_price = market.convert(asset{bytes, RAM_symbol}, core_symbol); 115 | ram_price.amount = (ram_price.amount * 200 + 199) / 199; // add ram fee 116 | return ram_price; 117 | } 118 | 119 | /** 120 | * Memo format 1: ACCOUNT_NAME:PUBLIC_KEY:EOS_FOR_CPU:RAM_AMOUNT_KB 121 | * First 2 parameters are mandatory, the rest are optional 122 | * If EOS_FOR_CPU and RAM_AMOUNT_KB is not given, default values are used. 123 | * So either 2 params, or 4 124 | * 125 | * Memo format 2: ACCOUNT_NAME:OWNER_KEY:ACTIVE_KEY:EOS_FOR_CPU:RAM_AMOUNT_KB 126 | * This is with separate owner and active keys. 127 | * So either 3 params or 5 128 | */ 129 | void parse_memo(const std::string& memo, struct account_t& out) { 130 | std::vector v; 131 | 132 | split(trim(memo), ":-", v); 133 | 134 | /* This is true for all above cases */ 135 | out.name = name{v[0]}; 136 | out.owner_key = abieos::string_to_public_key(v[1]); 137 | 138 | /* default values unless overwritten below */ 139 | out.stake_cpu = default_cpu_stake; 140 | out.ram_amount_bytes = default_ram_amount_bytes; 141 | 142 | size_t size = v.size(); 143 | switch(size) { 144 | case 2: 145 | out.active_key = out.owner_key; 146 | break; 147 | case 3: 148 | out.active_key = abieos::string_to_public_key(v[2]); 149 | break; 150 | case 4: 151 | out.active_key = out.owner_key; 152 | out.stake_cpu = asset_from_string(v[2]); 153 | out.ram_amount_bytes = bytes_from_string(v[3]); 154 | break; 155 | case 5: 156 | out.active_key = abieos::string_to_public_key(v[2]); 157 | out.stake_cpu = asset_from_string(v[3]); 158 | out.ram_amount_bytes = bytes_from_string(v[4]); 159 | break; 160 | } 161 | } 162 | 163 | uint32_t bytes_from_string(std::string str) { 164 | const auto ram_amount_kb = std::strtoul(str.c_str(), nullptr, 10); 165 | const auto ram_amount_bytes = ram_amount_kb * 1024; 166 | check(ram_amount_bytes > default_ram_amount_bytes, "Accounts require at least 3 KB of RAM"); 167 | return ram_amount_bytes; 168 | } 169 | 170 | asset asset_from_string(std::string str) { 171 | const auto amount = std::atoll(str.c_str()); 172 | asset money{amount, core_symbol}; 173 | money *= 10000; 174 | check(money.amount > 0, "Please stake a positive amount"); 175 | return money; 176 | } 177 | 178 | std::string trim(const string& str) { 179 | const size_t first = str.find_first_not_of(' '); 180 | const size_t last = str.find_last_not_of(' '); 181 | return (const std::string)str.substr(first, (last-first+1)); 182 | } 183 | 184 | void split(const string& s, const string& delimiters, vector& v) { 185 | string::size_type i = 0; 186 | string::size_type j = find_any(s, delimiters); 187 | 188 | while(j != string::npos) { 189 | v.push_back(s.substr(i, j-i)); 190 | i = ++j; 191 | j = find_any(s, delimiters, j); 192 | 193 | if(j == string::npos) { 194 | v.push_back(s.substr(i, s.length())); 195 | } 196 | } 197 | } 198 | 199 | string::size_type find_any(const string& s, const string& delimiters, const string::size_type start=0) { 200 | for(std::string::size_type i = start; i < s.size(); ++i) { 201 | if(is_any(s[i], delimiters)) { 202 | return i; 203 | } 204 | } 205 | return string::npos; 206 | } 207 | 208 | bool is_any(const char& s, const string& candidates) { 209 | for(const char& c : candidates) { 210 | if(s == c) { 211 | return true; 212 | } 213 | } 214 | return false; 215 | } 216 | 217 | 218 | 219 | void create_account(const asset quantity, struct account_t& data) { 220 | const key_weight owner_pubkey_weight{ 221 | .key = data.owner_key, 222 | .weight = 1, 223 | }; 224 | const key_weight active_pubkey_weight{ 225 | .key = data.active_key, 226 | .weight = 1, 227 | }; 228 | const authority owner{ 229 | .threshold = 1, 230 | .keys = {owner_pubkey_weight}, 231 | .accounts = {}, 232 | .waits = {} 233 | }; 234 | const authority active{ 235 | .threshold = 1, 236 | .keys = {active_pubkey_weight}, 237 | .accounts = {}, 238 | .waits = {} 239 | }; 240 | const newaccount new_account{ 241 | .creator = _self, 242 | .name = data.name, 243 | .owner = owner, 244 | .active = active 245 | }; 246 | const auto stake_cpu = data.stake_cpu; 247 | const auto stake_net = default_net_stake; 248 | const auto rent_cpu_amount = asset{10, core_symbol}; 249 | const auto ram_price = determine_ram_price(data.ram_amount_bytes); 250 | const auto ram_replace_amount = determine_ram_price(800); 251 | const auto fee = asset{std::max((quantity.amount + 119) / 200, 1000ll), core_symbol}; 252 | const auto remaining_balance = quantity - stake_cpu - stake_net - ram_price - fee - ram_replace_amount - rent_cpu_amount; 253 | check(remaining_balance.amount >= 0, "Not enough money"); 254 | 255 | action( 256 | permission_level{ _self, "active"_n, }, 257 | "eosio"_n, 258 | "newaccount"_n, 259 | new_account 260 | ).send(); 261 | 262 | action( 263 | permission_level{ _self, "active"_n}, 264 | "eosio"_n, 265 | "buyram"_n, 266 | make_tuple(_self, data.name, ram_price) 267 | ).send(); 268 | 269 | action( 270 | permission_level{ _self, "active"_n}, 271 | "eosio"_n, 272 | "buyram"_n, 273 | make_tuple(_self, _self, ram_replace_amount) 274 | ).send(); 275 | 276 | action( 277 | permission_level{ _self, "active"_n}, 278 | "eosio.token"_n, 279 | "transfer"_n, 280 | make_tuple(_self, "saccountfees"_n, fee, std::string("Account creation fee")) 281 | ).send(); 282 | 283 | action( 284 | permission_level{ _self, "active"_n}, 285 | "eosio"_n, 286 | "delegatebw"_n, 287 | make_tuple(_self, data.name, stake_net, stake_cpu, true) 288 | ).send(); 289 | 290 | // action( 291 | // permission_level{ _self, "active"_n}, 292 | // "eosio"_n, 293 | // "deposit"_n, 294 | // make_tuple(_self, rent_cpu_amount) 295 | // ).send(); 296 | // 297 | // action( 298 | // permission_level{ _self, "active"_n}, 299 | // "eosio"_n, 300 | // "rentcpu"_n, 301 | // make_tuple(_self, data.name, rent_cpu_amount, asset{0, core_symbol}) 302 | // ).send(); 303 | 304 | if(remaining_balance.amount > 0) { 305 | action( 306 | permission_level{ _self, "active"_n}, 307 | "eosio.token"_n, 308 | "transfer"_n, 309 | make_tuple(_self, data.name, remaining_balance, std::string("Initial balance")) 310 | ).send(); 311 | } 312 | } 313 | }; 314 | --------------------------------------------------------------------------------