@mybucks.online/core
Version:
Core library for Mybucks.online — a seedless wallet and gifting wallet: disposable gift wallets, fund and share via secure 1-click URL. Browser-only (no servers, database, or install). Cryptography and wallet primitives for airdrops, giveaways, campaigns,
120 lines • 4.99 kB
JavaScript
import { Buffer } from "buffer";
import { nanoid } from "nanoid";
import zxcvbn from "zxcvbn";
import { PASSPHRASE_MIN_LENGTH, PASSPHRASE_MAX_LENGTH, PIN_MIN_LENGTH, PIN_MAX_LENGTH, } from "./credentials.js";
const LEGACY_URL_DELIMITER = "\u0002";
// Version byte for the default (non-legacy) token format.
// Uses a compact length-prefixed encoding for passphrase, pin and network.
const TOKEN_VERSION_COMPACT = 0x02;
const NETWORKS = [
"ethereum",
"polygon",
"arbitrum",
"optimism",
"bsc",
"avalanche",
"base",
"tron",
];
/**
* Generates a gifting-link token by encoding passphrase, pin and network, with random padding.
* The gifting-link lets users send full ownership of a wallet account (e.g. gifting or airdrops).
* Passphrase and PIN are validated by length (see PASSPHRASE_MIN/MAX_LENGTH, PIN_MIN/MAX_LENGTH) and zxcvbn; invalid or weak values return null.
* When legacy is false, payload is compact length-prefixed (version 0x02) to avoid concatenation ambiguity and keep the URL fragment short; when true, uses LEGACY_URL_DELIMITER concatenation.
*
* @param passphrase - Length in [PASSPHRASE_MIN_LENGTH, PASSPHRASE_MAX_LENGTH], zxcvbn score >= 3
* @param pin - Length in [PIN_MIN_LENGTH, PIN_MAX_LENGTH], zxcvbn score >= 1
* @param network - ethereum | polygon | arbitrum | optimism | bsc | avalanche | base | tron
* @param legacy - When true, LEGACY_URL_DELIMITER concatenation; when false, compact length-prefixed encoding
* @returns Token string suitable to append to `https://app.mybucks.online#wallet=`, or null if invalid/weak
*/
export function generateToken(passphrase, pin, network, legacy = false) {
if (!passphrase || !pin || !network) {
return null;
}
if (!NETWORKS.find((n) => n === network)) {
return null;
}
const passphraseLen = passphrase.length;
if (passphraseLen < PASSPHRASE_MIN_LENGTH ||
passphraseLen > PASSPHRASE_MAX_LENGTH) {
return null;
}
const pinLen = pin.length;
if (pinLen < PIN_MIN_LENGTH || pinLen > PIN_MAX_LENGTH) {
return null;
}
if (zxcvbn(passphrase).score < 3) {
return null;
}
if (zxcvbn(pin).score < 1) {
return null;
}
let payloadBuffer;
if (legacy) {
payloadBuffer = Buffer.from(passphrase + LEGACY_URL_DELIMITER + pin + LEGACY_URL_DELIMITER + network, "utf-8");
}
else {
// Default format: compact length-prefixed encoding.
const passphraseBytes = Buffer.from(passphrase, "utf-8");
const pinBytes = Buffer.from(pin, "utf-8");
const networkBytes = Buffer.from(network, "utf-8");
payloadBuffer = Buffer.concat([
Buffer.from([TOKEN_VERSION_COMPACT]),
Buffer.from([passphraseBytes.length]),
passphraseBytes,
Buffer.from([pinBytes.length]),
pinBytes,
Buffer.from([networkBytes.length]),
networkBytes,
]);
}
// Convert Base64 to Base64URL so token remains safe in URL hash/query contexts.
const base64Encoded = payloadBuffer
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
const padding = nanoid(12);
return padding.slice(0, 6) + base64Encoded + padding.slice(6);
}
/**
* Parses a gifting-link token produced by {@link generateToken}.
* Tokens whose payload starts with 0x02 are decoded as compact length-prefixed; otherwise payload is treated as legacy (UTF-8 + LEGACY_URL_DELIMITER).
*
* @param token - Token string returned by generateToken()
* @returns `{ passphrase, pin, network, legacy }` — legacy is true if token used legacy format, false if compact-encoded
*/
export function parseToken(token) {
const payload = token.slice(6, token.length - 6);
// Normalize payload for robust URL transport:
// - URLSearchParams converts "+" to " "
// - base64url may use "-" and "_" and omit padding
const normalized = payload
.replace(/ /g, "+")
.replace(/-/g, "+")
.replace(/_/g, "/");
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
const decoded = Buffer.from(padded, "base64");
if (decoded[0] === TOKEN_VERSION_COMPACT) {
let i = 1;
const lenP = decoded[i++];
const passphrase = decoded.subarray(i, i + lenP).toString("utf-8");
i += lenP;
const lenI = decoded[i++];
const pin = decoded.subarray(i, i + lenI).toString("utf-8");
i += lenI;
const lenN = decoded[i++];
const network = decoded.subarray(i, i + lenN).toString("utf-8");
return { passphrase, pin, network, legacy: false };
}
const str = decoded.toString("utf-8");
const [passphrase, pin, network] = str.split(LEGACY_URL_DELIMITER);
return {
passphrase: passphrase ?? "",
pin: pin ?? "",
network: network ?? "",
legacy: true,
};
}
//# sourceMappingURL=token.js.map