diff --git a/.env.example b/.env.example index bc605b7..afda051 100644 --- a/.env.example +++ b/.env.example @@ -51,3 +51,5 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" XMR_NETWORK_PORT=28083 XMR_WALLET_NAME=test XMR_TIP_ADDRESS= +XMR_EXPLORER_BASE_URL=https://testnet.xmrchain.net/tx/ +XMR_DAEMON_IP=127.0.0.1 diff --git a/README.md b/README.md index 0081d80..3d87a17 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,16 @@ sudo apt install php-bcmath supervisor ./monero-wallet-rpc --testnet --rpc-bind-port 28083 --disable-rpc-login --wallet-dir . ``` -## Seeding the Database - -You must seed addresses first and be connected to the daemon. +## Creating the Database ``` php artisan migrate ``` +## Creating Initial Wallet (Set Wallet Name in .env) + ``` -php artisan db:seed --class=AddressSeeder +php artisan db:seed --class=WalletSeeder ``` ## Cron Job to process payments @@ -44,6 +44,10 @@ Then set up 1 cron job to call * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1 ``` +## Config file + +Config options are in the .env.example file which should copied into a .env file + ## This Project Uses Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: diff --git a/app/Http/Controllers/AddressController.php b/app/Http/Controllers/AddressController.php deleted file mode 100644 index 2b9ded8..0000000 --- a/app/Http/Controllers/AddressController.php +++ /dev/null @@ -1,85 +0,0 @@ - json_encode(Meme::get(), JSON_PRETTY_PRINT), + 'memes_example' => json_encode(Meme::limit(1)->get(), JSON_PRETTY_PRINT), 'memes_endpoint' => url('api/memes'), ]; return view('api', ['data' => $data]); diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php new file mode 100644 index 0000000..eea84c9 --- /dev/null +++ b/app/Http/Controllers/ContactController.php @@ -0,0 +1,14 @@ + $data]); + } +} diff --git a/app/Http/Controllers/MemeController.php b/app/Http/Controllers/MemeController.php index 11fe5a8..756d854 100644 --- a/app/Http/Controllers/MemeController.php +++ b/app/Http/Controllers/MemeController.php @@ -7,6 +7,7 @@ use App\Models\User; use App\Models\Address; use Illuminate\Http\Request; use chillerlan\QRCode\{QRCode, QROptions}; +use MoneroIntegrations\MoneroPhp\walletRPC; class MemeController extends Controller { @@ -77,11 +78,8 @@ class MemeController extends Controller 'image' => ['required', 'image'], ]); $user = \Auth::user(); - $used_ids = Meme::pluck('address_id')->toArray(); - $address = Address::whereNotIn('id', $used_ids)->first('id'); - return Meme::create([ + Meme::create([ 'user_id' => $user->id, - 'address_id' => $address->id, 'title' => $request->input('title'), 'caption' => $request->input('caption'), 'image' => $request->file('image'), @@ -96,7 +94,7 @@ class MemeController extends Controller */ public function show($id) { - $meme = Meme::where('id', $id)->with(['user', 'tips', 'address'])->firstOrFail(); + $meme = Meme::where('id', $id)->with(['user', 'tips'])->firstOrFail(); $share = \Share::page(url()->current(), $meme->title, ['class' => 'fa-lg', 'target' => '_blank']) ->facebook() @@ -108,7 +106,7 @@ class MemeController extends Controller $data = [ 'meme' => $meme, - 'qr' => (new QRCode)->render($meme->address->address), + 'qr' => (new QRCode)->render($meme->address), 'share' => preg_replace("//", "", $share), ]; @@ -141,10 +139,19 @@ class MemeController extends Controller public function approve($id) { if (\Auth::user()->is_admin === 1) { - $meme = Meme::withoutGlobalScope('approved')->find($id); - $meme->is_approved = 1; - $meme->save(); - return redirect()->away(url()->previous()); + try { + $walletRPC = new walletRPC(config('app.xmr_daemon_ip'), config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet + $open_wallet = $walletRPC->open_wallet(config('app.xmr_wallet_name'), ''); + $account = $walletRPC->create_account(); + $meme = Meme::withoutGlobalScope('approved')->find($id); + $meme->is_approved = 1; + $meme->account_index = $account['account_index']; + $meme->address = $account['address']; + $meme->save(); + return redirect()->away(url()->previous()); + } catch (\Exception $e) { + + } } } diff --git a/app/Jobs/ProcessPayments.php b/app/Jobs/ProcessPayments.php index 52c2e6d..7d85eef 100644 --- a/app/Jobs/ProcessPayments.php +++ b/app/Jobs/ProcessPayments.php @@ -14,15 +14,12 @@ use App\Models\Address; use App\Models\Meme; use MoneroIntegrations\MoneroPhp\walletRPC; -// Can put code below inside functions to debug the Monero library -// ini_set('display_errors', 1); -// ini_set('display_startup_errors', 1); -// error_reporting(E_ALL); - class ProcessPayments implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $timeout = 3600; + /** * Create a new job instance. * @@ -47,23 +44,26 @@ class ProcessPayments implements ShouldQueue public function get_transactions() { try { - $walletRPC = new walletRPC('127.0.0.1', config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet + $walletRPC = new walletRPC(config('app.xmr_daemon_ip'), config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet $open_wallet = $walletRPC->open_wallet(config('app.xmr_wallet_name'), ''); - $get_transfers = $walletRPC->get_transfers('all', true); - if (isset($get_transfers['in'])) { - foreach ($get_transfers['in'] as $transfer) { - $address = Address::where('address', $transfer['address'])->first(); - $tip_exists = Tip::where('txid', $transfer['txid'])->first(); - if ($address && !$tip_exists) { - $tip = new Tip(); - $tip->address_id = $address->id; - $tip->amount = $transfer['amount']; - $tip->txid = $transfer['txid']; - $tip->is_deposit = 1; - $tip->save(); - $meme = Meme::where('address_id', $address->id)->firstOrFail(); - $meme->payment_pending = 1; - $meme->save(); + $account_indexes = Meme::pluck('account_index')->toArray(); + foreach ($account_indexes as $account_index) { + $get_transfers = $walletRPC->get_transfers('in', $account_index); + if (isset($get_transfers['in'])) { + foreach ($get_transfers['in'] as $transfer) { + $meme = Meme::where('address', $transfer['address'])->first(); + $tip_exists = Tip::where('txid', $transfer['txid'])->first(); + if ($meme && !$tip_exists) { + $tip = new Tip(); + $tip->meme_id = $meme->id; + $tip->amount = $transfer['amount']; + $tip->txid = $transfer['txid']; + $tip->is_deposit = 1; + $tip->save(); + $meme = Meme::where('address', $transfer['address'])->firstOrFail(); + $meme->payment_pending = 1; + $meme->save(); + } } } } @@ -76,20 +76,23 @@ class ProcessPayments implements ShouldQueue public function payout() { try { - $walletRPC = new walletRPC('127.0.0.1', config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet + $walletRPC = new walletRPC(config('app.xmr_daemon_ip'), config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet $open_wallet = $walletRPC->open_wallet(config('app.xmr_wallet_name'), ''); - $meme = Meme::where('payment_pending', 1)->firstOrFail(); - if ($meme->user->address) { - $send_funds = $walletRPC->sweep_all($meme->user->address, $meme->address->address_index); - if ($send_funds['amount_list']) { - $tip = new Tip; - $tip->address_id = $meme->address_id; - $tip->amount = $send_funds['amount_list'][0]; - $tip->txid = $send_funds['tx_hash_list'][0]; - $tip->is_deposit = 0; - $tip->save(); - $meme->payment_pending = 0; - $meme->save(); + $memes = Meme::where('payment_pending', 1)->get(); + foreach ($memes as $meme) { + $balance = $walletRPC->get_balance($meme->account_index); + if ($balance['balance'] === $balance['unlocked_balance']) { + $send_funds = $walletRPC->sweep_all($meme->user->address, '', $meme->account_index); + if ($send_funds['amount_list']) { + $tip = new Tip; + $tip->meme_id = $meme->id; + $tip->amount = $send_funds['amount_list'][0]; + $tip->txid = $send_funds['tx_hash_list'][0]; + $tip->is_deposit = 0; + $tip->save(); + $meme->payment_pending = 0; + $meme->save(); + } } } $walletRPC->close_wallet(); diff --git a/app/Models/Address.php b/app/Models/Address.php deleted file mode 100644 index 14f5391..0000000 --- a/app/Models/Address.php +++ /dev/null @@ -1,11 +0,0 @@ -belongsTo(User::class); } - public function address() - { - return $this->belongsTo(Address::class); - } - public function tips() { - return $this->hasManyThrough(Tip::class, Address::class, 'id', 'address_id', 'address_id', 'id')->orderBy('created_at', 'DESC'); + return $this->hasMany(Tip::class)->orderByDesc('created_at'); } public function getMemeTipsTotalAttribute() @@ -43,6 +45,11 @@ class Meme extends Model return $this->tips->where('is_deposit', 1)->sum('amount_formatted'); } + public function getImageUrlAttribute() + { + return url($this->image); + } + public function setImageAttribute($value) { $attribute_name = "image"; diff --git a/app/Models/Tip.php b/app/Models/Tip.php index c31eeaf..fe3350d 100644 --- a/app/Models/Tip.php +++ b/app/Models/Tip.php @@ -9,13 +9,18 @@ class Tip extends Model { use HasFactory; - protected $appends = ['amount_formatted']; + protected $appends = ['amount_formatted', 'tx_url']; public function getAmountFormattedAttribute() { return number_format(($this->amount)*(pow(10, -12)), 8, '.', ''); } + public function getTxUrlAttribute() + { + return config('app.xmr_explorer_base_url') . $this->txid; + } + public function address() { return $this->belongsTo(Address::class); diff --git a/app/Models/User.php b/app/Models/User.php index 3d58326..577235f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -51,8 +51,7 @@ class User extends Authenticatable public function tips() { - return $this->hasManyThrough(Tip::class, Meme::class, 'address_id', 'id', 'id', 'address_id'); - return $this->hasManyThrough(Tip::class, Meme::class, 'id', 'address_id'); + return $this->hasManyThrough(Tip::class, Meme::class, 'user_id'); } public function getMemesTotalAttribute() diff --git a/app/Rules/ValidateAddress.php b/app/Rules/ValidateAddress.php index 47a696f..121bd2a 100644 --- a/app/Rules/ValidateAddress.php +++ b/app/Rules/ValidateAddress.php @@ -26,7 +26,7 @@ class ValidateAddress implements Rule */ public function passes($attribute, $value) { - $walletRPC = new walletRPC('127.0.0.1', config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet + $walletRPC = new walletRPC(config('app.xmr_daemon_ip'), config('app.xmr_network_port')); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet $open_wallet = $walletRPC->open_wallet(config('app.xmr_wallet_name'), ''); $validate_address = $walletRPC->validate_address($value); return $validate_address['valid']; diff --git a/config/app.php b/config/app.php index c07bf91..43b2aaa 100644 --- a/config/app.php +++ b/config/app.php @@ -11,6 +11,8 @@ return [ 'xmr_wallet_name' => env('XMR_WALLET_NAME', 'Test'), 'xmr_network_port' => env('XMR_NETWORK_PORT', 28083), 'xmr_tip_address' => env('XMR_TIP_ADDRESS', null), + 'xmr_explorer_base_url' => env('XMR_EXPLORER_BASE_URL', 'https://testnet.xmrchain.net/tx/'), + 'xmr_daemon_ip' => env('XMR_DAEMON_IP', '127.0.0.1'), /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2021_07_11_052707_create_addresses_table.php b/database/migrations/2021_07_11_052707_create_addresses_table.php deleted file mode 100644 index 2b92992..0000000 --- a/database/migrations/2021_07_11_052707_create_addresses_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->string('address', 95)->unique(); - $table->integer('address_index'); - $table->string('label'); - $table->boolean('used'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('addresses'); - } -} diff --git a/database/migrations/2021_07_12_020134_create_memes_table.php b/database/migrations/2021_07_12_020134_create_memes_table.php index 42c5fa6..51d42f2 100644 --- a/database/migrations/2021_07_12_020134_create_memes_table.php +++ b/database/migrations/2021_07_12_020134_create_memes_table.php @@ -16,12 +16,13 @@ class CreateMemesTable extends Migration Schema::create('memes', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); - $table->foreignId('address_id')->constrained(); $table->string('title'); $table->string('caption')->nullable(); $table->string('image')->nullable(); $table->boolean('is_approved')->default(0); $table->boolean('payment_pending')->default(0); + $table->string('address', 95)->nullable()->unique(); + $table->integer('account_index')->nullable()->unique(); $table->softDeletes(); $table->timestamps(); }); diff --git a/database/migrations/2021_07_12_021524_create_tips_table.php b/database/migrations/2021_07_12_021524_create_tips_table.php index a42b987..11d36e3 100644 --- a/database/migrations/2021_07_12_021524_create_tips_table.php +++ b/database/migrations/2021_07_12_021524_create_tips_table.php @@ -15,7 +15,7 @@ class CreateTipsTable extends Migration { Schema::create('tips', function (Blueprint $table) { $table->id(); - $table->foreignId('address_id')->constrained(); + $table->foreignId('meme_id')->constrained(); $table->bigInteger('amount'); $table->string('txid')->unique(); $table->boolean('is_deposit'); diff --git a/database/seeders/AddressSeeder.php b/database/seeders/AddressSeeder.php deleted file mode 100644 index 30abd2b..0000000 --- a/database/seeders/AddressSeeder.php +++ /dev/null @@ -1,36 +0,0 @@ -create_wallet(config('app.xmr_wallet_name'), ''); // Creates a new wallet named memes with no passphrase. Comment this line and edit the next line to use your own wallet - } catch (\Exception $e) { - dump($e->getMessage()); - } - - try { - $open_wallet = $walletRPC->open_wallet(config('app.xmr_wallet_name'), ''); - for ($i=0; $i < 100; $i++) { - $create_address = $walletRPC->create_address(0, 'Example'); - } - $get_address = $walletRPC->get_address(); - \DB::table('addresses')->insertOrIgnore($get_address['addresses']); - $walletRPC->close_wallet(); - } catch (\Exception $e) { - dump($e->getMessage()); - } - } -} diff --git a/database/seeders/WalletSeeder.php b/database/seeders/WalletSeeder.php new file mode 100644 index 0000000..ab7272a --- /dev/null +++ b/database/seeders/WalletSeeder.php @@ -0,0 +1,24 @@ +create_wallet(config('app.xmr_wallet_name'), ''); // Creates a new wallet named memes with no passphrase. Comment this line and edit the next line to use your own wallet + } catch (\Exception $e) { + dump($e->getMessage()); + } + } +} diff --git a/public/css/app_dark.css b/public/css/app_dark.css index 7d4d21a..9ad9d1f 100644 --- a/public/css/app_dark.css +++ b/public/css/app_dark.css @@ -16142,8 +16142,9 @@ readers do not read off random characters that represent icons */ .grid-memes .card-img-top { max-width: 100% !important; - width: inherit; - max-height: 20vw; + -o-object-fit: cover; + object-fit: cover; + max-height: 250px; } #social-links li { diff --git a/public/css/app_light.css b/public/css/app_light.css index 02b4034..4b77a88 100644 --- a/public/css/app_light.css +++ b/public/css/app_light.css @@ -16142,8 +16142,9 @@ readers do not read off random characters that represent icons */ .grid-memes .card-img-top { max-width: 100% !important; - width: inherit; - max-height: 20vw; + -o-object-fit: cover; + object-fit: cover; + max-height: 250px; } #social-links li { diff --git a/public/mix-manifest.json b/public/mix-manifest.json index f207d34..50dcaab 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,7 +1,7 @@ { "/js/app.js": "/js/app.js?id=a4f7ea48d857a5e8d88e", "/js/manifest.js": "/js/manifest.js?id=d7335e682eb6c876d5bc", - "/css/app_dark.css": "/css/app_dark.css?id=77995f3b0798a182ca9f", - "/css/app_light.css": "/css/app_light.css?id=8971b6e0fc76badb2856", + "/css/app_dark.css": "/css/app_dark.css?id=e9edb5a2a73bc761692d", + "/css/app_light.css": "/css/app_light.css?id=da42f1c3ae153b59ac52", "/js/vendor.js": "/js/vendor.js?id=964b623e0af1bb38e06e" } diff --git a/resources/sass/_common.scss b/resources/sass/_common.scss index 1e72cc6..6950b33 100644 --- a/resources/sass/_common.scss +++ b/resources/sass/_common.scss @@ -23,8 +23,8 @@ .grid-memes .card-img-top { max-width: 100% !important; - width: inherit; - max-height: 20vw; + object-fit: cover; + max-height: 250px; } #social-links li { diff --git a/resources/views/api.blade.php b/resources/views/api.blade.php index 1d3dfbf..0550ab4 100644 --- a/resources/views/api.blade.php +++ b/resources/views/api.blade.php @@ -15,7 +15,7 @@ Rate Limit: 60 Request Per Hour
- {{ $data['memes_example'] }}
+ {{ $data['memes_example'] }}
diff --git a/resources/views/contact.blade.php b/resources/views/contact.blade.php
new file mode 100644
index 0000000..b875a82
--- /dev/null
+++ b/resources/views/contact.blade.php
@@ -0,0 +1,22 @@
+@extends('layouts.app')
+
+@section('content')
+Payments usually go out a few times an hour however there can be a delay. Please submit an issue if you have not been paid after 24 hours.
+Monero is a decentralized organization. This website was created by a developer who likes Monero and should not be seen as "official".
+Please submit an issue with proof you are the creator of the meme.
+{{ $data['meme']->address->address }}
+{{ $data['meme']->address }}
Total Earnings: {{ $data['user']->tips_total }}
Total Memes: {{ $data['user']->memes_total }} -