Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • monero-project/ccs-back
  • xiphon/ccs-back
  • Fudin/ccs-back
  • john_r365/ccs-back
  • plowsofff/ccs-back
5 results
Show changes
Showing
with 2548 additions and 3694 deletions
<?php
namespace App\Repository;
use GuzzleHttp\Client;
class MergeRequest implements Proposal
{
private $merge_request;
public function __construct($merge_request)
{
$this->merge_request = $merge_request;
}
public function iid() : int
{
return $this->merge_request->iid;
}
public function id() : int
{
return $this->merge_request->id;
}
public function url() : string
{
return $this->merge_request->web_url;
}
public function title() : string
{
return $this->merge_request->title;
}
public function author() : string
{
return $this->merge_request->author->username;
}
public function created_at() : int
{
return strtotime($this->merge_request->created_at);
}
}
class Gitlab implements Repository
{
private $client;
private $base_url;
private const stateToString = [ State::Opened => 'opened',
State::Merged => 'merged'];
public function __construct(Client $client, string $repository_url)
{
$this->client = $client;
$this->base_url = $repository_url;
}
public function mergeRequests($state)
{
$url = $this->base_url . '/merge_requests?scope=all&per_page=50&state=' . Self::stateToString[$state];
$response = $this->client->request('GET', $url);
return collect(json_decode($response->getBody()))->map(function ($merge_request) {
return new MergeRequest($merge_request);
});
}
public function getNewFiles($merge_request)
{
$url = $this->base_url . '/merge_requests/' . $merge_request->iid() . '/changes';
$response = $this->client->request('GET', $url);
return collect(json_decode($response->getBody())->changes)->filter(function ($change) {
return $change->new_file;
})->map(function ($change) {
return $change->new_path;
});
}
}
<?php
namespace App\Repository;
use GuzzleHttp\Client;
interface State
{
const Merged = 0;
const Opened = 1;
const All = 2;
}
interface Proposal
{
public function id() : int;
public function url() : string;
public function title() : string;
public function author() : string;
public function created_at() : int;
}
interface Repository
{
public function __construct(Client $client, string $repository_url);
public function mergeRequests($state);
public function getNewFiles(Proposal $proposal);
}
...@@ -4,7 +4,7 @@ use Faker\Generator as Faker; ...@@ -4,7 +4,7 @@ use Faker\Generator as Faker;
$factory->define(\App\Deposit::class, function (Faker $faker) { $factory->define(\App\Deposit::class, function (Faker $faker) {
return [ return [
'payment_id' => $faker->sha256, 'subaddr_index' => $faker->randomNumber(),
'amount' => $faker->randomNumber(2), 'amount' => $faker->randomNumber(2),
'time_received' => $faker->dateTime, 'time_received' => $faker->dateTime,
'tx_id' => $faker->sha256, 'tx_id' => $faker->sha256,
......
...@@ -3,11 +3,10 @@ ...@@ -3,11 +3,10 @@
use Faker\Generator as Faker; use Faker\Generator as Faker;
$factory->define(\App\Project::class, function (Faker $faker) { $factory->define(\App\Project::class, function (Faker $faker) {
$state = $faker->randomElement(['OPENED', 'IDEA', 'FUNDING-REQUIRED', 'WORK-IN-PROGRESS', 'COMPLETED']); $state = $faker->randomElement(['FUNDING-REQUIRED', 'WORK-IN-PROGRESS', 'COMPLETED']);
$status = $faker->randomElement(['opened', 'closed', 'locked', 'merged']);
return [ return [
'title' => $faker->sentence(), 'title' => $faker->sentence(),
'payment_id' => $faker->sha256, 'subaddr_index' => $faker->randomNumber(),
'address' => $faker->sha256, 'address' => $faker->sha256,
'address_uri' => "monero:{$faker->sha256}", 'address_uri' => "monero:{$faker->sha256}",
'qr_code' => $faker->file(), 'qr_code' => $faker->file(),
......
...@@ -17,7 +17,7 @@ class CreateProjectsTable extends Migration ...@@ -17,7 +17,7 @@ class CreateProjectsTable extends Migration
$table->increments('id'); $table->increments('id');
$table->string('author'); $table->string('author');
$table->string('title'); $table->string('title');
$table->string('payment_id')->nullable(); $table->unsignedInteger('subaddr_index')->nullable();
$table->string('address')->nullable(); $table->string('address')->nullable();
$table->string('address_uri')->nullable(); $table->string('address_uri')->nullable();
$table->string('qr_code')->nullable(); $table->string('qr_code')->nullable();
......
...@@ -15,8 +15,8 @@ class CreateDepositsTable extends Migration ...@@ -15,8 +15,8 @@ class CreateDepositsTable extends Migration
{ {
Schema::create('deposits', function (Blueprint $table) { Schema::create('deposits', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->string('payment_id');
$table->unsignedInteger('confirmations')->default(0); $table->unsignedInteger('confirmations')->default(0);
$table->unsignedInteger('subaddr_index');
$table->string('amount'); $table->string('amount');
$table->dateTime('time_received'); $table->dateTime('time_received');
$table->string('tx_id'); $table->string('tx_id');
......
...@@ -12,7 +12,7 @@ class ProjectsTableSeeder extends Seeder ...@@ -12,7 +12,7 @@ class ProjectsTableSeeder extends Seeder
public function run() public function run()
{ {
factory(\App\Project::class, 20)->create()->each(function ($p) { factory(\App\Project::class, 20)->create()->each(function ($p) {
$p->deposits()->saveMany(factory(\App\Deposit::class, rand(0,12))->make(['payment_id' => $p->payment_id])); $p->deposits()->saveMany(factory(\App\Deposit::class, rand(0,12))->make(['subaddr_index' => $p->subaddr_index]));
}); });
} }
} }
<?php
namespace GitLab;
use GuzzleHttp\Client;
class Connection
{
/** @var Client */
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function mergeRequests($state = 'all') {
$url = env('GITLAB_URL') . '/merge_requests?scope=all&per_page=50&state='. $state;
$response = $this->client->request('GET', $url, ['headers' => ['Private-Token' => env('GITLAB_ACCESS_TOKEN')]]);
return collect(json_decode($response->getBody()));
}
public function getNewFiles($merge_request_iid) {
$url = env('GITLAB_URL') . '/merge_requests/' . $merge_request_iid . '/changes';
$response = $this->client->request('GET', $url, ['headers' => ['Private-Token' => env('GITLAB_ACCESS_TOKEN')]]);
$deserialized = collect(json_decode($response->getBody()));
$result = [];
foreach ($deserialized['changes'] as $change) {
if ($change->new_file) {
$result[] = $change->new_path;
}
}
return $result;
}
}
\ No newline at end of file
...@@ -37,11 +37,11 @@ interface WalletManager ...@@ -37,11 +37,11 @@ interface WalletManager
public function blockHeight(); public function blockHeight();
/** /**
* Creates a new integrated address * Creates a new subaddress
* *
* @return array ['integrated_address', 'payment_id'] * @return array ['address', 'address_index']
*/ */
public function createIntegratedAddress(); public function createSubaddress();
/** /**
* Gets any incoming transactions * Gets any incoming transactions
...@@ -64,12 +64,12 @@ interface WalletManager ...@@ -64,12 +64,12 @@ interface WalletManager
* creates a uri for easier wallet parsing * creates a uri for easier wallet parsing
* *
* @param string $address address comprising of primary, sub or integrated address * @param string $address address comprising of primary, sub or integrated address
* @param string $paymentId payment id when not using integrated addresses
* @param int $amount atomic amount requested * @param int $amount atomic amount requested
* @param string $paymentId payment id when not using integrated addresses
* *
* @return string the uri string which can be used to generate a QR code * @return string the uri string which can be used to generate a QR code
*/ */
public function createUri($address, $paymentId = null, $amount = null); public function createUri($address, $amount = null, $paymentId = null);
/** /**
* creates a random 64 char payment id * creates a random 64 char payment id
......
...@@ -16,7 +16,7 @@ class Transaction ...@@ -16,7 +16,7 @@ class Transaction
public $confirmations; public $confirmations;
public $payment_id; public $subaddr_index;
public $block_height; public $block_height;
...@@ -31,7 +31,7 @@ class Transaction ...@@ -31,7 +31,7 @@ class Transaction
* @param $timeReceived * @param $timeReceived
* @param $paymentId * @param $paymentId
*/ */
public function __construct($id, $amount, $address, $confirmations, $time, $timeReceived, $paymentId = null, $blockheight = null) public function __construct($id, $amount, $address, $confirmations, $time, $timeReceived, $subaddr_index = null, $blockheight = null)
{ {
$this->amount = $amount; $this->amount = $amount;
$this->time_received = $timeReceived; $this->time_received = $timeReceived;
...@@ -39,7 +39,7 @@ class Transaction ...@@ -39,7 +39,7 @@ class Transaction
$this->address = $address; $this->address = $address;
$this->id = $id; $this->id = $id;
$this->confirmations = $confirmations; $this->confirmations = $confirmations;
$this->payment_id = $paymentId; $this->subaddr_index = $subaddr_index;
$this->block_height = $blockheight; $this->block_height = $blockheight;
$this->correctTimeRecieved(); $this->correctTimeRecieved();
} }
......
...@@ -28,15 +28,15 @@ class Wallet ...@@ -28,15 +28,15 @@ class Wallet
public function getPaymentAddress() public function getPaymentAddress()
{ {
$integratedAddress = $this->createIntegratedAddress(); $subaddress = $this->createSubaddress();
if (!$integratedAddress) { if (!$subaddress) {
return ['address' => 'not valid', 'expiration_time' => 900]; return ['address' => 'not valid', 'expiration_time' => 900];
} }
$project = new Project(); $project = new Project();
$project->payment_id = $integratedAddress['payment_id']; $project->subaddr_index = $subaddress['address_index'];
$project->save(); $project->save();
return ['address' => $integratedAddress['integrated_address'], 'paymentId' => $integratedAddress['payment_id']]; return ['address' => $subaddress['address'], 'subaddr_index' => $subaddress['address_index']];
} }
/** /**
...@@ -49,71 +49,41 @@ class Wallet ...@@ -49,71 +49,41 @@ class Wallet
return $this->client->balance(); return $this->client->balance();
} }
public function mempoolTransfers()
{
return $this->client->incomingTransfers();
}
public function bulkPayments($paymentIds)
{
$blockBuffer = 10;
return $this->client->payments($paymentIds, intval($this->wallet->last_scanned_block_height) - $blockBuffer);
}
/** /**
* Scans the monero blockchain for transactions for the payment ids * @param $min_height
* * @param $account_index
* @param $blockheight
* @param $paymentIDs
* *
* @return array|Transaction * @return \Illuminate\Support\Collection
*/ */
public function scanBlocks($blockheight, $paymentIDs) public function scanIncomingTransfers($min_height = 0, $account_index = 0)
{ {
$response = $this->bulkPayments($paymentIDs); $response = $this->client->incomingTransfers($min_height);
$address = $this->getAddress(); if (!$response) {
$transactions = []; return collect([]);
if ($response && isset($response['payments'])) {
foreach ($response['payments'] as $payment) {
$transaction = new Transaction(
$payment['tx_hash'],
$payment['amount'],
$address,
$blockheight - $payment['block_height'],
0,
Carbon::now(),
$payment['payment_id'],
$payment['block_height']
);
$transactions[] = $transaction;
}
} }
return collect($transactions);
}
/**
* @param $blockheight
*
* @return \Illuminate\Support\Collection
*/
public function scanMempool($blockheight)
{
$address = $this->getAddress();
$transactions = []; $transactions = [];
$response = $this->mempoolTransfers(); const toScan = ['pool', 'in'];
if ($response && isset($response['pool'])) { foreach (toScan as $entry) {
foreach ($response['pool'] as $payment) { if (!isset($response[$entry])) {
continue;
}
foreach ($response[$entry] as $payment) {
if $payment['subaddr_index']['major'] != $account_index {
continue;
}
if ($payment['locked']) {
continue;
}
$transaction = new Transaction( $transaction = new Transaction(
$payment['txid'], $payment['txid'],
$payment['amount'], $payment['amount'],
$address, $payment['address'],
0, $payment['confirmations'],
0, 0,
Carbon::now(), Carbon::now(),
$payment['payment_id'], $payment['subaddr_index']['minor'],
$blockheight $payment['height']
); );
$transactions[] = $transaction; $transactions[] = $transaction;
} }
...@@ -143,37 +113,34 @@ class Wallet ...@@ -143,37 +113,34 @@ class Wallet
} }
/** /**
* Returns XMR integrated address * Returns XMR subaddress
* *
* @return mixed * @return mixed
*/ */
public function createIntegratedAddress() public function createSubaddress()
{ {
return $this->client->createIntegratedAddress(); return $this->client->createSubaddress();
} }
/** /**
* @param $amount
* @param $address * @param $address
* @param $paymentId * @param $amount
* *
* @return string * @return string
*/ */
public function createQrCodeString($amount, $address, $paymentId = ''): string public function createQrCodeString($address, $amount): string
{ {
// @todo add tx_payment_id support
// monero payment_id is passed through the address
return 'monero:'.$address.'?tx_amount='.$amount; return 'monero:'.$address.'?tx_amount='.$amount;
} }
/** /**
* gets all the payment_ids outstanding from the address_pool, we use these to check against the latest mined blocks * gets all the subaddr_indexes outstanding from the address_pool, we use these to check against the latest mined blocks
* *
* @return Collection * @return Collection
*/ */
public function getPaymentIds() public function getSubaddressIndexes()
{ {
return Project::pluck('payment_id'); //stop scanning for payment_ids after 24h return Project::pluck('subaddr_index'); //stop scanning for subaddr_index after 24h
} }
} }
<?php
namespace Monero;
interface WalletCommon
{
public static function digitsAfterTheRadixPoint() : int;
public function getPaymentAddress();
public function scanIncomingTransfers($min_height = 0);
public function blockHeight() : int;
public function createQrCodeString($address, $amount = null) : string;
}
...@@ -6,8 +6,13 @@ use App\Project; ...@@ -6,8 +6,13 @@ use App\Project;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class WalletOld class WalletOld implements WalletCommon
{ {
public static function digitsAfterTheRadixPoint() : int
{
return 12;
}
/** /**
* WalletOld constructor. * WalletOld constructor.
* *
...@@ -15,7 +20,9 @@ class WalletOld ...@@ -15,7 +20,9 @@ class WalletOld
*/ */
public function __construct($client = null) public function __construct($client = null)
{ {
$this->client = $client ?: new jsonRPCClient(env('RPC_URL')); $this->client = $client ?: new jsonRPCClient([ 'username' => env('RPC_USER'),
'password' => env('RPC_PASSWORD'),
'url' => env('RPC_URL')]);
} }
/** /**
...@@ -28,12 +35,12 @@ class WalletOld ...@@ -28,12 +35,12 @@ class WalletOld
public function getPaymentAddress() public function getPaymentAddress()
{ {
$integratedAddress = $this->createIntegratedAddress(); $subaddress = $this->createSubaddress();
if (!$integratedAddress) { if (!$subaddress) {
return ['address' => 'not valid']; return ['address' => 'not valid'];
} }
return ['address' => $integratedAddress['integrated_address'], 'paymentId' => $integratedAddress['payment_id']]; return ['address' => $subaddress['address'], 'subaddr_index' => $subaddress['address_index']];
} }
/** /**
...@@ -46,71 +53,40 @@ class WalletOld ...@@ -46,71 +53,40 @@ class WalletOld
return $this->client->balance(); return $this->client->balance();
} }
public function mempoolTransfers()
{
return $this->client->incomingTransfers();
}
public function bulkPayments($paymentIds)
{
$blockBuffer = 10;
return $this->client->payments($paymentIds, intval($this->wallet->last_scanned_block_height) - $blockBuffer);
}
/**
* Scans the monero blockchain for transactions for the payment ids
*
* @param int $blockheight
* @param Collection $paymentIDs
*
* @return array|Transaction
*/
public function scanBlocks($blockheight, $paymentIDs)
{
$response = $this->bulkPayments($paymentIDs);
$address = $this->getAddress();
$transactions = [];
if ($response && isset($response['payments'])) {
foreach ($response['payments'] as $payment) {
$transaction = new Transaction(
$payment['tx_hash'],
$payment['amount'],
$address,
$blockheight - $payment['block_height'],
0,
Carbon::now(),
$payment['payment_id'],
$payment['block_height']
);
$transactions[] = $transaction;
}
}
return collect($transactions);
}
/** /**
* @param $blockheight * @param $blockheight
* @param $account_index
* *
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function scanMempool($blockheight) public function scanIncomingTransfers($min_height = 0, $account_index = 0)
{ {
$address = $this->getAddress(); $response = $this->client->incomingTransfers($min_height);
if (!$response) {
return collect([]);
}
$transactions = []; $transactions = [];
$response = $this->mempoolTransfers(); foreach (['pool', 'in'] as $entry) {
if ($response && isset($response['pool'])) { if (!isset($response[$entry])) {
foreach ($response['pool'] as $payment) { continue;
}
foreach ($response[$entry] as $payment) {
if ($payment['subaddr_index']['major'] != $account_index) {
continue;
}
if ($payment['locked']) {
continue;
}
$transaction = new Transaction( $transaction = new Transaction(
$payment['txid'], $payment['txid'],
$payment['amount'], $payment['amount'],
$address, $payment['address'],
0, $payment['confirmations'],
0, 0,
Carbon::now(), Carbon::now(),
$payment['payment_id'], $payment['subaddr_index']['minor'],
$blockheight $payment['height']
); );
$transactions[] = $transaction; $transactions[] = $transaction;
} }
...@@ -124,7 +100,7 @@ class WalletOld ...@@ -124,7 +100,7 @@ class WalletOld
* *
* @return int * @return int
*/ */
public function blockHeight() public function blockHeight() : int
{ {
return $this->client->blockHeight(); return $this->client->blockHeight();
} }
...@@ -140,35 +116,34 @@ class WalletOld ...@@ -140,35 +116,34 @@ class WalletOld
} }
/** /**
* Returns XMR integrated address * Returns XMR subaddress
* *
* @return mixed * @return mixed
*/ */
public function createIntegratedAddress() public function createSubaddress()
{ {
return $this->client->createIntegratedAddress(); return $this->client->createSubaddress();
} }
/** /**
* @param $amount
* @param $address * @param $address
* @param $paymentId * @param $amount
* *
* @return string * @return string
*/ */
public function createQrCodeString($address, $amount = null, $paymentId = null): string public function createQrCodeString($address, $amount = null): string
{ {
return $this->client->createUri($address, $amount, $paymentId); return $this->client->createUri($address, $amount);
} }
/** /**
* gets all the payment_ids outstanding from the address_pool, we use these to check against the latest mined blocks * gets all the subaddr_indexes outstanding from the address_pool, we use these to check against the latest mined blocks
* *
* @return Collection * @return Collection
*/ */
public function getPaymentIds() public function getSubaddressIndexes()
{ {
return Project::pluck('payment_id'); //stop scanning for payment_ids after 24h return Project::pluck('subaddr_index'); //stop scanning for subaddr_index after 24h
} }
} }
<?php
namespace Monero;
use Carbon\Carbon;
class WalletZcoin implements WalletCommon
{
private $rpc;
public static function digitsAfterTheRadixPoint() : int
{
return 8;
}
public function __construct()
{
$this->rpc = new jsonRpcBase([ 'auth_type' => 'basic',
'username' => env('RPC_USER'),
'password' => env('RPC_PASSWORD'),
'url' => env('RPC_URL')]);
}
public function getPaymentAddress()
{
return ['address' => $this->rpc->request('getnewaddress')];
}
private function decodeTxAmount(string $tx_amount) : int
{
$tx_amount = str_replace(',', '.', $tx_amount);
$amount = explode('.', $tx_amount);
if (sizeof($amount) < 1 || sizeof($amount) > 2) {
throw new \Exception('Failed to decode tx amount ' . $tx_amount);
}
$fraction = $amount[1] ?? "";
if (strlen($fraction) > $this->digitsAfterTheRadixPoint()) {
throw new \Exception('Failed to decode tx amount, too many digits after the redix point ' . $tx_amount);
}
$amount = $amount[0] . str_pad($fraction, $this->digitsAfterTheRadixPoint(), '0');
$amount = intval($amount);
if ($amount == 0) {
throw new \Exception('Failed to convert tx amount to int ' . $tx_amount);
}
return $amount;
}
public function scanIncomingTransfers($skip_txes = 0)
{
return collect($this->rpc->request('listtransactions', ['', 100, $skip_txes]))->filter(function ($tx) {
return $tx['category'] == 'receive';
})->map(function ($tx) {
return new Transaction(
$tx['txid'],
$this->decodeTxAmount($tx['amount']),
$tx['address'],
$tx['confirmations'],
0,
Carbon::now(),
0,
isset($tx['blockhash']) ? $this->blockHeightByHash($tx['blockhash']) : 0
);
});
}
public function blockHeight() : int
{
return $this->rpc->request('getblockcount');
}
public function createQrCodeString($address, $amount = null) : string
{
return 'zcoin:' . $address . ($amount ? '?amount=' . $amount : '');
}
private function blockHeightByHash($block_hash) : int
{
return $this->rpc->request('getblockheader', [$block_hash])['height'];
}
}
...@@ -12,18 +12,7 @@ use Illuminate\Support\Facades\Log; ...@@ -12,18 +12,7 @@ use Illuminate\Support\Facades\Log;
*/ */
class jsonRPCClient implements Contracts\WalletManager class jsonRPCClient implements Contracts\WalletManager
{ {
private $rpc;
/** @var string */
private $username = 'test2';
/** @var string */
private $password = 'test2';
/** @var string */
private $url = 'http://127.0.0.1:28080/json_rpc';
/** @var Client|null */
private $client;
/** /**
* JsonRPCClient constructor. * JsonRPCClient constructor.
...@@ -32,20 +21,7 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -32,20 +21,7 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function __construct($options, $client = null) public function __construct($options, $client = null)
{ {
$this->username = $options['username'] ?? $this->username; $this->rpc = new jsonRpcBase($options, $client);
$this->password = $options['password'] ?? $this->password;
$this->url = $options['url'] ?? $this->url;
if (empty($client)) {
$client = new Client([
'base_uri' => $this->url,
'headers' => [
'Content-Type' => 'application/json',
]
]);
}
$this->client = $client;
} }
/** /**
...@@ -55,7 +31,7 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -55,7 +31,7 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function balance() : int public function balance() : int
{ {
$response = $this->request('get_balance'); $response = $this->rpc->request('get_balance');
return $response['balance']; return $response['balance'];
} }
...@@ -66,7 +42,7 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -66,7 +42,7 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function unlockedBalance() : int public function unlockedBalance() : int
{ {
$response = $this->request('get_balance'); $response = $this->rpc->request('get_balance');
return $response['unlocked_balance']; return $response['unlocked_balance'];
} }
...@@ -77,7 +53,7 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -77,7 +53,7 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function address() : string public function address() : string
{ {
$response = $this->request('get_address'); $response = $this->rpc->request('get_address');
return $response['address']; return $response['address'];
} }
...@@ -88,17 +64,21 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -88,17 +64,21 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function blockHeight() : int public function blockHeight() : int
{ {
$response = $this->request('get_height'); $response = $this->rpc->request('get_height');
return $response['height']; return $response['height'];
} }
/** /**
* Creates a new integrated address * Creates a new subaddress
* *
* @return array ['integrated_address', 'payment_id'] * @param int $account_index account index to create subaddress (maajor index)
* @param string $label label to assign to new subaddress
*
* @return array ['address', 'address_index']
*/ */
public function createIntegratedAddress() : array public function createSubaddress($account_index = 0, $label = '') : array
{ {
$response = $this->request('make_integrated_address'); $response = $this->rpc->request('create_address', ['account_index' => $account_index, 'label' => $label]);
return $response; return $response;
} }
...@@ -109,7 +89,7 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -109,7 +89,7 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function incomingTransfers($min_height = 0) : array public function incomingTransfers($min_height = 0) : array
{ {
$response = $this->request('get_transfers', ['pool' => true, 'in' => true, 'min_height' => $min_height, 'filter_by_height' => $min_height > 0 ? true : false]); $response = $this->rpc->request('get_transfers', ['pool' => true, 'in' => true, 'min_height' => $min_height, 'filter_by_height' => $min_height > 0 ? true : false]);
return $response; return $response;
} }
...@@ -124,7 +104,7 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -124,7 +104,7 @@ class jsonRPCClient implements Contracts\WalletManager
*/ */
public function payments($paymentIds, $minHeight) : array public function payments($paymentIds, $minHeight) : array
{ {
$response = $this->request('get_bulk_payments', ['payment_ids' => $paymentIds, 'min_block_height' => $minHeight]); $response = $this->rpc->request('get_bulk_payments', ['payment_ids' => $paymentIds, 'min_block_height' => $minHeight]);
return $response; return $response;
} }
...@@ -133,14 +113,14 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -133,14 +113,14 @@ class jsonRPCClient implements Contracts\WalletManager
* creates a uri for easier wallet parsing * creates a uri for easier wallet parsing
* *
* @param string $address address comprising of primary, sub or integrated address * @param string $address address comprising of primary, sub or integrated address
* @param string $paymentId payment id when not using integrated addresses
* @param int $amount atomic amount requested * @param int $amount atomic amount requested
* @param string $paymentId payment id when not using integrated addresses
* *
* @return string the uri string which can be used to generate a QR code * @return string the uri string which can be used to generate a QR code
*/ */
public function createUri($address, $paymentId = null, $amount = null) : string public function createUri($address, $amount = null, $paymentId = null) : string
{ {
$response = $this->request('make_uri', ['address' => $address, 'amount' => $amount, 'payment_id' => $paymentId]); $response = $this->rpc->request('make_uri', ['address' => $address, 'amount' => $amount, 'payment_id' => $paymentId]);
return $response['uri']; return $response['uri'];
} }
...@@ -154,60 +134,4 @@ class jsonRPCClient implements Contracts\WalletManager ...@@ -154,60 +134,4 @@ class jsonRPCClient implements Contracts\WalletManager
{ {
return bin2hex(openssl_random_pseudo_bytes(32)); return bin2hex(openssl_random_pseudo_bytes(32));
} }
/**
* Sets up the request data body
*
* @param string $method name of the rpc command
* @param array $params associative array of variables being passed to the method
*
* @return false|string will return a json string or false
*/
private function preparePayload($method, $params)
{
$payload = [
'jsonrpc' => '2.0',
'id' => '0',
'method' => $method,
'params' => $params,
];
return json_encode($payload);
}
/**
* Send off request to rpc server
*
* @param string $method name of the rpc command
* @param array $params associative array of variables being passed to the method
*
* @return mixed the rpc query result
*
* @throws \RuntimeException
*/
protected function request(string $method, array $params = [])
{
$payload = $this->preparePayload($method, $params);
try {
$response = $this->client->request('POST', '',[
'auth' => [$this->username, $this->password, 'digest'],
'body' => $payload,
'headers' => [
'Content-Type' => 'application/json',
]
]);
$body = $response->getBody();
} catch (GuzzleException $exception) {
Log::error($exception);
throw new \RuntimeException('Connection to node unsuccessful');
}
$result = json_decode((string) $body, true);
if (isset($result['error'])) {
throw new \RuntimeException($result['error']['message']);
}
return $result['result'];
}
} }
<?php
namespace Monero;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;
class jsonRpcBase
{
/** @var string */
private $username = 'test2';
/** @var string */
private $password = 'test2';
/** @var string */
private $url = 'http://127.0.0.1:28080/json_rpc';
/** @var Client|null */
private $client;
private $auth_type;
/**
* JsonRPCClient constructor.
* @param array $options
* @param null $client
*/
public function __construct($options, $client = null)
{
$this->username = $options['username'] ?? $this->username;
$this->password = $options['password'] ?? $this->password;
$this->url = $options['url'] ?? $this->url;
$this->auth_type = $options['auth_type'] ?? 'digest';
if (empty($client)) {
$client = new Client([
'base_uri' => $this->url,
'headers' => [
'Content-Type' => 'application/json',
]
]);
}
$this->client = $client;
}
/**
* Sets up the request data body
*
* @param string $method name of the rpc command
* @param array $params associative array of variables being passed to the method
*
* @return false|string will return a json string or false
*/
private function preparePayload($method, $params)
{
$payload = [
'jsonrpc' => '2.0',
'id' => '0',
'method' => $method,
'params' => $params,
];
return json_encode($payload);
}
/**
* Send off request to rpc server
*
* @param string $method name of the rpc command
* @param array $params associative array of variables being passed to the method
*
* @return mixed the rpc query result
*
* @throws \RuntimeException
*/
public function request(string $method, array $params = [])
{
$payload = $this->preparePayload($method, $params);
try {
$response = $this->client->request('POST', '',[
'auth' => [$this->username, $this->password, $this->auth_type],
'body' => $payload,
'headers' => [
'Content-Type' => 'application/json',
]
]);
$body = $response->getBody();
} catch (GuzzleException $exception) {
Log::error($exception);
error_log($exception);
throw new \RuntimeException('Connection to node ' . $this->url . ' unsuccessful');
}
$result = json_decode((string) $body, true);
if (isset($result['error'])) {
throw new \RuntimeException($result['error']['message']);
}
return $result['result'];
}
}
This diff is collapsed.
## About Monero FFS # About Monero CCS
Monero FFS is a simple web system for capturing donations made to fund community projects Monero CCS is a simple web system for capturing donations made to fund community projects
# CCS Deployment Quickstart
## Requirements
```
mysql >= 5.7.7
php >= 7.1
```
## Deployment
```
apt update
apt install -y cron git jekyll mysql-server nginx php php-curl php-fpm php-gd php-mbstring php-mysql php-xml unzip
```
Install `Composer` following the instructions at https://getcomposer.org/download/
Checkout and configure CCS backend, frontend and proposals repositories (replace `<REPOSITORY_CCS_BACKEND>`, `<REPOSITORY_CCS_FRONTEND>`, `<REPOSITORY_CCS_PROPOSALS>` with the actual URLs)
```
cd /var/www/html
git clone <REPOSITORY_CCS_BACKEND>
git clone <REPOSITORY_CCS_FRONTEND>
git clone <REPOSITORY_CCS_PROPOSALS> ccs-back/storage/app/proposals
rm -rf ccs-front/proposals
ln -s /var/www/html/ccs-back/storage/app/proposals ccs-front/proposals
ln -fs /var/www/html/ccs-back/storage/app/proposals.json ccs-front/_data/proposals.json
ln -fs /var/www/html/ccs-back/storage/app/complete.json ccs-front/_data/completed-proposals.json
cd ccs-back
composer update
cp .env.example .env
```
Spin up MYSQL server, create new database, user and grant user access to it
Open `.env` in editor of choice and edit the following lines:
> `COIN` - choose one of supported coins: `monero` or `zcoin`
> `REPOSITORY_URL` - CCS proposals Github URL or GitLab API endpoint (e.g. https://\<GITLAB_DOMAIN>/api/v4/projects/\<PROJECT_ID>)>
> `GITHUB_ACCESS_TOKEN` - leave empty if you are not using Github or visit https://github.com/settings/tokens to generate new `public_repo` token
```
APP_URL=http://<HOSTNAME>
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<DB_NAME>
DB_USERNAME=<DB_USER_NAME>
DB_PASSWORD=<DB_USER_PASSWORD>
RPC_URL=http://127.0.0.1:28080/json_rpc
RPC_USER=
RPC_PASSWORD=
COIN=<COIN>
REPOSITORY_URL=<REPOSITORY_URL>
GITHUB_ACCESS_TOKEN=
```
Initialize the system
```
php artisan migrate:fresh
php artisan up
php artisan key:generate
php artisan proposal:process
php artisan proposal:update
```
Grant `www-data` user access to the files
```
cd ..
chown -R www-data ccs-back/
chown -R www-data ccs-front/
```
Remove Nginx example config
```
rm /etc/nginx/sites-enabled/default
```
Create new file `/etc/nginx/sites-enabled/ccs` in editor of choice and paste the following lines replacing `<HOSTNAME>` and `<PHP_VERSION>` with appropriate values
```
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html/ccs-front/_site/;
index index.php index.html;
server_name <HOSTNAME>;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# pass the PHP scripts to FastCGI server
#
location ~ \.php$ {
root /var/www/html/ccs-back/public/;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php<PHP_VERSION>-fpm.sock;
}
}
```
```
service nginx reload
```
Set up a cron job that will run periodic updates (every minute) and generate static HTML files
```
* * * * * git -C /var/www/html/ccs-back/storage/app/proposals/ pull; php /var/www/html/ccs-back/artisan schedule:run; jekyll build --source /var/www/html/ccs-front --destination /var/www/html/ccs-front/_site
```
## Optional
Instead of scheduling a cron job you can run the following commands in no particular order
1. Update CCS system proposals intenal state
```
php /var/www/html/ccs-back/artisan proposal:process
php /var/www/html/ccs-back/artisan generate:addresses
php /var/www/html/ccs-back/artisan wallet:notify
php /var/www/html/ccs-back/artisan proposal:update
```
2. Process incoming donations
*Run it either on new block/tx notification or schedule it to run every minute or so*
```
php /var/www/html/ccs-back/artisan monero:notify
```
1. Generate static HTML files
```
jekyll build --source /var/www/html/ccs-front --destination /var/www/html/ccs-front/_site
```
2. Get the full list of processed transactions in JSON format
```
php /var/www/html/ccs-back/artisan deposit:list
```
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>FFS</title> <title>CCS - Donate {{$project->title}}</title>
<link rel="apple-touch-icon" sizes="180x180" href="/meta/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/meta/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/meta/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/meta/favicon-32x32.png">
...@@ -27,72 +27,84 @@ ...@@ -27,72 +27,84 @@
<div class="page-wrapper"> <div class="page-wrapper">
<div class="mob-nav"> <div class="mob-nav">
<input class="burger-check" id="mobile-burger" type="checkbox"><label for="mobile-burger" class="burger"></label> <input class="burger-check" id="mobile-burger" type="checkbox"><label for="mobile-burger" class="burger"></label>
<div class="slide-in-nav"> <div class="slide-in-nav">
<div class="container slide-in"> <div class="container slide-in">
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="text-center nav-item mob"> <div class="text-center nav-item mob">
<a href="/forum-funding-system/ideas/" class="top-link">Ideas</a> <a href="/ideas/" class="top-link">Ideas</a>
</div> </div>
<div class="text-center nav-item mob"> <div class="text-center nav-item mob">
<a href="/forum-funding-system/funding-required/">Funding Required</a> <a href="/funding-required/">Funding Required</a>
</div> </div>
<div class="text-center nav-item mob"> <div class="text-center nav-item mob">
<a href="/forum-funding-system/work-in-progress/">Work in Progress</a> <a href="/work-in-progress/">Work in Progress</a>
</div> </div>
<div class="text-center nav-item mob"> <div class="text-center nav-item mob">
<a href="/forum-funding-system/completed-proposals/">Completed Tasks</a> <a href="/completed-proposals/">Completed Tasks</a>
</div> </div>
<div class="text-center nav-item mob"> <div class="text-center nav-item mob">
<a href="/forum-funding-system/completed-proposals/">Back to Getmonero.org</a> <a href="/donate/index.html">Donate</a>
</div> </div>
</div> <div class="text-center nav-item mob">
</div> <a href="/completed-proposals/">Back to Getmonero.org</a>
</div> </div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="desktop-nav"> <div class="desktop-nav">
<nav class="container"> <nav class="container">
<div class="row middle-xs">
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-4">
<p class="site-name"><a href="/">Community Crowdfunding System</a></p>
</div>
<div class="col-lg-8 col-md-8 col-sm-8 items end-xs">
<div class="row end-xs middle-xs">
<div class="col-md-12">
<div class="dropdown">
<label for="desktopdrop">CCS Stages<div class="arrow-down"></div></label>
<input class="burger-checkdropdown" id="desktopdrop" type="checkbox">
<div class="dropdown-content">
<a href="/ideas/">Ideas</a>
<a href="/funding-required/">Funding Required</a>
<a href="/work-in-progress/">Work in Progress</a>
<a href="/completed-proposals/">Completed Tasks</a>
</div>
</div>
<a href="https://getmonero.org">Getmonero.org</a>
<a href="/donate/" class="donate-btn">Donate</a>
</div>
</div>
</div>
</div>
</nav>
</div>
<div class="mob bot-nav white-nav">
<div class="row middle-xs"> <div class="row middle-xs">
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-4"> <div class="col-xs-12">
<a href="/forum-funding-system/"><img src="/img/monero-logo.png" alt="Monero Logo" class="monero-logo"></a> <p class="site-name"><a href="
</div>
<div class="col-lg-8 col-md-8 col-sm-8 items end-xs"> /
<div class="row end-xs middle-xs">
<div class="col-md-12"> ">Community Crowdfunding System</a></p>
<a href="/forum-funding-system/ideas/" class="top-link">Ideas</a>
<a href="/forum-funding-system/funding-required/">Funding Required</a>
<a href="/forum-funding-system/work-in-progress/">Work in Progress</a>
<a href="/forum-funding-system/completed-proposals/">Completed Tasks</a>
<a href="/forum-funding-system/completed-proposals/">Getmonero.org</a>
</div>
</div>
</div> </div>
</div> </div>
</nav>
</div>
<div class="mob bot-nav white-nav">
<div class="row middle-xs">
<div class="col-xs-6">
<a href="/
"><img src="/img/monero-logo.png" alt="Monero Logo" class="monero-logo"></a>
</div>
</div> </div>
</div>
<div class="site-wrap ffs-proposal ffs-con"> <div class="site-wrap ffs-proposal ffs-con">
<div class="container ffs-breadcrumbs"> <div class="container ffs-breadcrumbs">
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<p><a href="/forum-funding-system/">Forum Funding System</a></p> <p><a href="/">Community Crowdfunding System</a></p>
<p><a href="/forum-funding-system/funding-required/">Funding Required</a></p> <p><a href="/funding-required/">Funding Required</a></p>
<p><a href="/forum-funding-system/funding-required/">{{$project->title}}</a></p> <p><a href="/proposals/{{pathinfo($project->filename, PATHINFO_FILENAME)}}.html">{{$project->title}}</a></p>
<p class="bread-active">Contribute</p> <p class="bread-active">Contribute</p>
</div> </div>
</div> </div>
...@@ -101,15 +113,14 @@ ...@@ -101,15 +113,14 @@
<section class="container full"> <section class="container full">
<div class="info-block"> <div class="info-block">
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<h2>{{$project->title}}</h2> <h2>{{$project->title}}</h2>
<div class="row middle-xs between-xs"> <p class="author-list">{{$project->author}}</p>
<p class="author-list"><span><img src="/img/author-filled.png"></span>{{$project->author}}</p> <p class="date-list">{{date('F j, Y', strtotime($project->created_at))}}</p>
<p class="date-list"><span><img src="/img/calendar.png"></span>{{$project->github_created_at}}</p> <p class="date-list contributor">{{$project->contributions}}</p>
<p class="bar-fund-status">Raised <span class="progress-number-funded">{{$project->raised_amount}}</span> of <span class="progress-number-goal">{{$project->target_amount}}</span> XMR</p> <p class="bar-fund-status">Raised <span class="progress-number-funded">{{$project->raised_amount}}</span> of <span class="progress-number-goal">{{$project->target_amount}}</span> XMR</p>
</div>
<div class="progress-bar"> <div class="progress-bar">
<span class="fund-progress" style="width: [PERCENTAGE HERE]%"></span> <span class="fund-progress" style="width: {{min(100, intval($project->raised_amount * 100 / $project->target_amount))}}%"></span>
</div> </div>
<p>Your contribution should be visible within 5 minutes of you sending your contribution. If for some reason it is not there, please contact a member of the Core Team!</p> <p>Your contribution should be visible within 5 minutes of you sending your contribution. If for some reason it is not there, please contact a member of the Core Team!</p>
</div> </div>
...@@ -129,32 +140,21 @@ ...@@ -129,32 +140,21 @@
<p>1. Choose the amount of XMR you wish to contribute to this proposal</p> <p>1. Choose the amount of XMR you wish to contribute to this proposal</p>
<p>2. Scan this QR code or tap to open in your Monero wallet app:</p> <p>2. Scan this QR code or tap to open in your Monero wallet app:</p>
<p> <p>
<a href="{{$project->address_uri}}" class="qr"><img src="{{ $project->qrCodeSrc}}"/></a> <a href="{{$project->address_uri}}" class="qr"><img src="{{$project->qrCodeSrc}}"/></a>
</p> </p>
<p>3. Send! Thank you! You are amazing!</p> <p>3. Send! Thank you! You are amazing!</p>
</div> </div>
</div> </div>
<input class="input" name="tabs" type="radio" id="tab-2"/> <input class="input" name="tabs" type="radio" id="tab-2"/>
<label class="label" for="tab-2">Integrated Address</label> <label class="label" for="tab-2">Address</label>
<div class="panel col-xs-12"> <div class="panel col-xs-12">
<div class="panel-segment"> <div class="panel-segment">
<h3>Contribute using an integrated address</h3> <h3>Contribute using an address</h3>
<p>1. Choose the amount of XMR you wish to contribute to this proposal</p> <p>1. Choose the amount of XMR you wish to contribute to this proposal</p>
<p>2. Enter the following XMR address:</p> <p class="string">{{$project->address}}</p> <p>2. Enter the following XMR address:</p> <p class="string">{{$project->address}}</p>
<p>3. Send! Thank you! You are amazing!</p> <p>3. Send! Thank you! You are amazing!</p>
</div> </div>
</div> </div>
<input class="input" name="tabs" type="radio" id="tab-3"/>
<label class="label" for="tab-3">Payment ID</label>
<div class="panel col-xs-12">
<div class="panel-segment">
<h3>Contribute using a payment ID</h3>
<p>1. Choose the amount of XMR you wish to contribute to this proposal</p>
<p>2. Enter the following XMR address:</p> <p class="string">{{ env('WALLET_ADDRESS') }}</p>
<p>3. Enter the following payment ID that is unique to this proposal:</p> <p class="string">{{$project->payment_id}}</p>
<p>4. Send! Thank you! You're amazing!</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -162,21 +162,26 @@ ...@@ -162,21 +162,26 @@
</div> </div>
<footer class="container-fluid"> <footer class="container-fluid">
<div class="container"> <div class="container">
<div class="row around-xs footer-wrapper"> <div class="row center-xs footer-wrapper">
<div class="col-md-8 col-sm-10 col-xs-12">
<div class="row center-xs"> <h3>Donate to the Monero Project</h3>
<div class="social-icons"> <p>By donating to the following Monero address (General Fund), you are supporting the Monero Project. If you wish to donate to a specific proposal, please see <a href="/funding-required/index.html" class="white gf">Funding Required</a>.</p>
</div> <p><a href="monero:44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A" class="qr"><img src="/img/donate-monero.png" /></a></p>
<p class="gf-address">44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A</p>
</div>
</div>
</div>
<div class="row center-xs">
<div class="footer-links"> <div class="footer-links">
<ul class="list-unstyled list-inline"> <ul class="list-unstyled list-inline">
<li><a href="https://repo.getmonero.org/monero-project/ccs-front" class="white footer-link">CCS Front End Repo</a></li>
<li><a href="https://repo.getmonero.org/monero-project/ccs-back" class="white footer-link">CCS Backend Repo</a></li>
<li><a href="https://repo.getmonero.org/monero-project/ccs-proposals" class="white footer-link">CCS Proposals Repo</a></li>
</ul> </ul>
</div> </div>
</div>
</div> </div>
</div> </footer>
</footer>
</div> </div>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -10,11 +10,11 @@ ...@@ -10,11 +10,11 @@
<tbody> <tbody>
@foreach ($projects as $project) @foreach ($projects as $project)
<tr> <tr>
<td><a href='{!! url('/projects/'.$project->payment_id); !!}'>{{ $project->payment_id }}</a></td> <td><a href='{!! url('/projects/'.$project->subaddr_index); !!}'>{{ $project->subaddr_index }}</a></td>
<td>{{$project->status}}</td> <td>{{$project->status}}</td>
<td>{{$project->amount_received}} XMR</td> <td>{{$project->raised_amount}} XMR</td>
<td>{{$project->target_amount}} XMR</td> <td>{{$project->target_amount}} XMR</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
\ No newline at end of file