Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 144 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,144 @@
# Redeemer
A simple, non-custodial 'Sweeping' client for PIVX Promos.
# Redeemer - Multi-Coin Redemption Interface

A lightweight, customizable interface for redeeming cryptocurrency promo codes with automatic theming per coin.

## Features

- **Multi-Coin Support**: Easily switch between different cryptocurrencies
- **Automatic Theming**: Each coin has its own visual theme with smooth transitions
- **Responsive Design**: Works across different screen sizes
- **Customizable**: Easy to add new coins and customize themes

## Theming System

### How Themes Work

Each coin in the system has its own theme defined by three main colors:

- **Primary Color**: Used for buttons, borders, and the coin icon
- **Secondary Color**: Used for text and subtle elements
- **Background Color**: Used for the page background

When switching between coins, the interface smoothly transitions between themes with animated color changes.

### Default Themes

| Coin | Primary Color | Secondary Color | Background Color |
|------|--------------|----------------|-----------------|
| PIVX | #5E4778 | #3C2F4B | #F0EBF8 |
| BTC | #F7931A | #4D3B24 | #FFF8E1 |
| LTC | #345D9D | #172D4F | #E9F0F9 |
| DASH | #008CE7 | #00456F | #E5F4FD |
| ZEC | #F4B728 | #795B14 | #FEF9E7 |

## Customization Guide

### Adding a New Coin

To add a new coin to the system, locate the `coins` array in `public/js/coins.js` and add a new entry:

```javascript
{
ticker: "XMR",
name: "Monero",
primaryColor: "#FF6600",
secondaryColor: "#4C3A1D",
backgroundColor: "#FFF1E5"
}
```

### Customizing Coin Logos

#### How the Logo System Works

The system uses a hybrid approach for coin icons:

1. **Image-First Approach**: When a coin is selected, the system first checks for an image file named after the coin's ticker in lowercase (e.g., `pivx.png`, `btc.png`) in the `public/images/` directory.

2. **Automatic Fallback**: If no matching image is found, the system displays the first letter of the coin's ticker in a colored circle using the coin's primary color.

#### Adding a Custom Logo

To add a custom logo for any coin:

1. Create or acquire an image file (preferably PNG with transparency)
2. Name it exactly after the coin's ticker in lowercase (e.g., `btc.png`, `pivx.png`)
3. Save it in the `public/images/` directory
4. The system will automatically detect and use your image

**Recommended Image Specifications:**
- Size: 40×40 pixels (larger images will be scaled)
- Format: PNG with transparency
- Shape: Preferably circular or square with transparent background

### Changing the Default Coin

To change which coin is selected by default:

1. In `public/js/redeemer.js`, locate the initialization code
2. Change the condition in the dropdown population to match your desired default coin:
```javascript
// Set PIVX as the default selected option
if (coin.ticker === "PIVX") {
option.selected = true;
}
```

3. Update the initial CSS variables in `public/style.css` to match the theme colors of your default coin

## Technical Details

### Theme Implementation

The theming system uses CSS variables defined in the `:root` selector for global theme colors. When a coin is selected:

1. JavaScript in `redeemer.js` updates these CSS variables with the selected coin's colors
2. CSS transitions ensure smooth color changes across all themed elements
3. The coin icon and title are updated to reflect the selected coin

### Image Detection

The system uses JavaScript's `Image` object to check if a logo image exists before attempting to use it:

```javascript
function imageExists(imagePath) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = imagePath;
});
}
```

This ensures graceful fallback to the letter-based icon if an image isn't available.

## Directory Structure

```
public/
├── images/ # Coin logo images
│ ├── btc.png # Bitcoin logo (example)
│ ├── pivx.png # PIVX logo (example)
│ └── ... # Other coin logos
├── js/ # JavaScript files
│ ├── coins.js # Coin configuration array
│ ├── redeemer.js # UI and theming logic
│ ├── sweep.js # Redemption logic
│ └── ... # Other JS files
├── index.html # Main HTML file
└── style.css # CSS styles
```

## URL Parameters

The application supports URL query parameters to pre-select options:

| Parameter | Description | Example |
|-----------|-------------|---------|
| `coin` | Auto-selects the specified coin | `?coin=BTC` |
| `code` | Pre-fills the promo code input | `?code=ABC123` |

Combined example: `https://redeemer.com/?coin=PIVX&code=PROMO2023`

You can use this feature to create direct links for specific coins and promo codes.
56 changes: 56 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Load environment variables from .env file
require('dotenv').config();

// Native Node modules
const path = require('path');
const http = require('http');
const https = require('https');
const fs = require('fs');
const cors = require('cors');

// NPM modules
const express = require('express');
const { redirectToHTTPS } = require('express-http-to-https');

// Setup Express
const app = express();
// No CORS
app.use(cors());
const port = process.env.PORT || 3000;
const sslCertPath = '/etc/letsencrypt/live/' + process.env.DOMAIN + '/';

// Serve static files from the 'public' directory, along with the root prefix if specified
app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
const router = require('./src/routes');
app.use(router);
// Startup
let server;
if (process.env.DOMAIN) {
// SSL options
const sslOptions = {
cert: fs.readFileSync(sslCertPath + 'fullchain.pem'),
key: fs.readFileSync(sslCertPath + 'privkey.pem')
};

// Start the HTTPS server
server = https.createServer(sslOptions, app);
server.listen(443, () => {
console.log(`HTTPS server running on port 443 --> https://${process.env.DOMAIN}`);
});

// Listen for HTTP requests for redirect purposes
const httpApp = express();
httpApp.use(redirectToHTTPS());
httpApp.listen(80);
} else {
// Start the HTTP server
server = http.createServer(app);
server.listen(port, () => {
console.log(`HTTP server running on port ${port} --> http://localhost:${port}`);
});
}

// Handle server errors
server.on('error', (error) => {
console.error('Server error:', error);
});
56 changes: 49 additions & 7 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@

<!--- IMPORTANT: CHAIN PARAMS BELOW, DO NOT EDIT UNLESS YOU'RE ABSOLUTELY CERTAIN YOU KNOW WHAT YOU'RE DOING --->
<script>
// In most BTC-derived coins, the below parameters can be found in the 'src/chainparams.cpp' Mainnet configuration.
// These below params share the same names as the CPP params, so finding and editing these is easy-peasy!

// These get changed when you select a coin from the drop down. Don't edit here as these are the defaults
/* chainparams */
const PUBKEY_ADDRESS = 30;
const SECRET_KEY = 212;
let PUBKEY_ADDRESS = 30;
let SECRET_KEY = 212;
let PRIVKEY_BYTE_LENGTH = 38;
</script>

<script type="text/javascript" src="js/lib/crypto-min.js"></script>
Expand All @@ -21,14 +20,57 @@
<script type="text/javascript" src="js/lib/jsbn.js"></script>
<script type="text/javascript" src="js/lib/ellipticcurve.js"></script>
<script type="text/javascript" src="js/lib/sha256.js"></script>
<script type="text/javascript" src="js/lib/Andreasha256.js"></script>

<script type="text/javascript" src="js/wallet.js"></script>
<script type="text/javascript" src="js/network.js"></script>
<script type="text/javascript" src="js/bitTrx.js"></script>
<script type="text/javascript" src="js/global.js"></script>

<!-- New file for all of our new code -->
<script type="text/javascript" src="js/sweep.js"></script>

<!-- Coin configuration -->
<script type="text/javascript" src="js/coins.js"></script>

<!-- Main application logic -->
<script type="text/javascript" src="js/redeemer.js"></script>

<!-- Link to the stylesheet -->
<link rel="stylesheet" href="style.css">
</head>

<body>
<!--
UTXO syncing (grabbing 'em all off Blockbook for the given privkey).
Sweeping those UTXOs to user-given-address.
-->

<!-- First step is to get the users privkey -->
<div class="centered-container">
<div class="coin-header">
<div id="coinIcon" class="coin-icon" aria-label="PIVX Logo">P</div>
<h2 id="coinTitle">PIVX Redeemer</h2>
</div>
<div class="form-container">
<label for="coinSelect">Select Coin</label><br>
<select id="coinSelect" name="coinSelect"></select><br>

<label for="PivCode">Promo Code</label><br>
<input type="text" id="PivCode" name="PivCode"><br>

<label id="addressLabel" for="sweepAddr">Address</label><br>
<input type="text" id="sweepAddr" name="sweepAddr"><br>

<center><button type="button" id="redeemBtn" onclick="Redeem()">Redeem</button></center>
</div>

<div id="derivingCode" class="derivingCode"></div>
<div id="trx" class="d-center redeemProgress redeemMsg" style="display:none!important;">
<span id="trxHeader" class="text">Signed Transaction</span>
<span id="trxText" class="textMsg">Failed to find coins from that Promo</span>
</div>
</div>

</body>

</html>
</html>
1 change: 1 addition & 0 deletions public/js/bitTrx.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
var o = {};
var buf = [];
var addrDecoded = btrx.addressDecode(address);
console.log(addrDecoded)
o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10);
buf.push(118); // OP_DUP
buf.push(169); // OP_HASH160
Expand Down
81 changes: 81 additions & 0 deletions public/js/coins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Coin configuration array
* Each coin has:
* - ticker: Short symbol for the coin
* - name: Full name of the cryptocurrency
* - Fee: This is the preset fee we will use when transferring coins on the network. This has to match with the batcher to make sense for the user
* - privatePrefix: This is a coin based parameter used when creating the signed tx
* - pubKeyAddress: Coin parameter
* - PrivKeyByteLength: Coin parameter
* - primaryColor: Main theme color (buttons, icons)
* - secondaryColor: Secondary theme color (text, labels)
* - backgroundColor: Page background color
*/
const coins = [
{
ticker: "PIVX",
name: "PIVX",
Fee: 0.00010000,
privatePrefix: 212,
pubKeyAddress: 30,
privKeyByteLength: 38,
primaryColor: "#5E4778",
secondaryColor: "#3C2F4B",
backgroundColor: "#F0EBF8"
},
{
ticker: "SCC",
name: "SCC",
Fee: 0.00010000,
privatePrefix: 253,
pubKeyAddress: 125,
privKeyByteLength: 38,
primaryColor: "#06aae9",
secondaryColor: "#0fcad5",
backgroundColor: "#223750"
},
{
ticker: "MRX",
name: "MRX",
Fee: 2.25000000,
privatePrefix: 85,
pubKeyAddress: 50,
privKeyByteLength: 38,
primaryColor: "#510457",
secondaryColor: "#2b032b",
backgroundColor: "#ceb8cf"
},
{
ticker: "PEP",
name: "PEPE",
Fee: 0.01000000,
privatePrefix: 158,
pubKeyAddress: 56,
privKeyByteLength: 38,
primaryColor: "#269b4d",
secondaryColor: "#000000",
backgroundColor: "#202337"
},
{
ticker: "DOGE",
name: "DOGE",
Fee: 0.01000000,
privatePrefix: 158,
pubKeyAddress: 30,
privKeyByteLength: 38,
primaryColor: "#e1b303",
secondaryColor: "#cb9800",
backgroundColor: "#f9f9f9"
},
{
ticker: "nMNSC",
name: "nMNSC",
Fee: 0.00010000,
privatePrefix: 82,
pubKeyAddress: 53,
privKeyByteLength: 38,
primaryColor: "#593196",
secondaryColor: "#231f20",
backgroundColor: "#3e2a45"
},
];
Loading