turnley.dev

PHP and MySQL code for faucet operators

Tools & Calculators

FRD-005

Cap what one visitor can earn you in a day

Pay each creative at most once per visitor per day. The ceiling becomes the number of creatives in rotation, which is a number you control.

Banner impressions are the easiest inventory to run up, because refreshing a page is free. Without a cap, a publisher with a browser extension and some patience earns as fast as they can reload.

Paying a given creative at most once per visitor per day turns that into a fixed ceiling. Thirty creatives in rotation means thirty paid impressions per visitor per day, no matter how many times they reload. Real visitors never reach the ceiling; farmers hit it in the first minute and then work for nothing.

Whether the cap is global or per publisher is a real decision. Global means the first publisher to show a creative to a visitor gets paid and the rest do not, which is stricter and slightly unfair to whoever shows it second. Per publisher is friendlier and multiplies the ceiling by the number of publishers a farmer controls.

PHP
/*
CREATE TABLE paid_views (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  creative_id INT UNSIGNED NOT NULL,
  visitor_key CHAR(32) NOT NULL,
  day DATE NOT NULL,
  UNIQUE KEY uq_paid (creative_id, visitor_key, day)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/

function credit_impression(PDO $pdo, int $creativeId, int $publisherId, string $visitorKey, int $units): bool
{
    try {
        // The unique key does the deciding. No SELECT-then-INSERT race.
        $pdo->prepare('INSERT INTO paid_views (creative_id, visitor_key, day) VALUES (?, ?, CURDATE())')
            ->execute([$creativeId, $visitorKey]);
    } catch (PDOException $e) {
        if ($e->errorInfo[1] === 1062) {
            return false;                    // already paid for today: show it, pay nothing
        }
        throw $e;
    }

    $pdo->prepare('UPDATE balances SET cash = cash + ? WHERE user_id = ?')
        ->execute([$units, $publisherId]);

    return true;
}

function visitor_key(string $ip, string $userAgent, string $dailySalt): string
{
    return md5($dailySalt . '|' . $ip . '|' . $userAgent);
}

Using it

Serve the creative regardless of whether the credit succeeded. The visitor should never be able to tell which impressions paid.

When picking a creative, prefer one this visitor has not been paid for today. That way a genuine reader works through the rotation instead of hitting the same unpaid banner repeatedly.

Add the publisher id to the unique key to make the cap per publisher instead of global.

What bites people

Rotate the salt daily so the visitor key cannot be correlated across days. It is a fraud key, not an identity.

Failed loads should not consume a slot. Only insert the row when you actually paid.

The table grows by one row per paid impression. Prune anything older than a couple of days; the cap only ever looks at today.

This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.

Also in Anti-Bot and Fraud