snacktesting
Version:
A CLI tool for sending USDC payments on Twitter and Farcaster using x402.
305 lines (301 loc) • 11 kB
JavaScript
import chalk from 'chalk';
import dotenv from 'dotenv';
import { Command } from 'commander';
import path from 'path';
import { fileURLToPath } from 'url';
import axios from 'axios';
import { privateKeyToAccount } from 'viem/accounts';
import { withPaymentInterceptor, decodeXPaymentResponse } from 'x402-axios';
import _gradientString from 'gradient-string';
import fs from 'fs';
import inquirer from 'inquirer';
// package.json
var package_default = {
name: "snacktesting",
version: "0.0.1",
description: "A CLI tool for sending USDC payments on Twitter and Farcaster using x402."};
var defaultColors = ["#F54180", "#338EF7"];
var gradientString = _gradientString(...defaultColors);
var logPrefix = gradientString("Snack Money CLI:");
var Logger = class {
constructor() {
}
static log(...args) {
console.log(...args);
}
static info(...args) {
console.info(...args.map((item) => chalk.blue(item)));
}
static success(...args) {
console.info(...args.map((item) => chalk.green(item)));
}
static warn(...args) {
console.warn(...args.map((item) => chalk.yellow(item)));
}
static error(...args) {
console.error(...args.map((item) => chalk.red(item)));
}
static grey(...args) {
console.log(...args.map((item) => chalk.gray(item)));
}
static gradient(content, options) {
this.log(
// eslint-disable-next-line prettier/prettier
_gradientString(...options?.colors ?? defaultColors)(String(content))
);
}
static prefix(type, ...args) {
return this[type](logPrefix, ...args);
}
static newLine(lines) {
if (!lines) lines = 1;
for (let i = 0; i < lines; i++) this.log();
}
};
// src/actions/pay.ts
async function payAction(cmd) {
const privateKey = process.env.PRIVATE_KEY;
const baseURL = process.env.RESOURCE_SERVER_URL;
const endpointPath = process.env.ENDPOINT_PATH;
if (!baseURL || !privateKey || !endpointPath) {
Logger.error("Missing required environment variables");
process.exit(1);
}
const { identity, username, amount } = cmd;
const allowedIdentities = ["twitter", "farcaster"];
if (!allowedIdentities.includes(identity.toLowerCase())) {
Logger.error("receiver_identity must be either 'twitter' or 'farcaster'");
process.exit(1);
}
const parsedAmount = parseFloat(amount);
if (isNaN(parsedAmount)) {
Logger.error("Amount must be a valid number, e.g., 0.01");
process.exit(1);
}
const account = privateKeyToAccount(privateKey);
const api = withPaymentInterceptor(axios.create({ baseURL }), account);
try {
const response = await api.post(endpointPath, {
amount: parsedAmount,
currency: "USDC",
type: "social-network",
sender_username: "snackmoney-agent-x402",
receiver_username: username,
receiver_identity: identity
});
Logger.log("response", response.data);
const paymentResponse = decodeXPaymentResponse(
// eslint-disable-next-line prettier/prettier
response.headers["x-payment-response"]
);
Logger.log(paymentResponse);
} catch (error) {
Logger.error("error", error);
}
}
var __filename = fileURLToPath(import.meta.url);
var __dirname = path.dirname(__filename);
var localEnvPath = path.resolve(__dirname, "../.env");
async function envAction() {
const { privateKey } = await inquirer.prompt([
{
name: "privateKey",
type: "password",
message: "Enter your Ethereum PRIVATE_KEY:",
mask: "*",
// ✅ target dist/.env after build
validate: (input) => /^0x[0-9a-fA-F]{64}$/.test(input) ? true : "Must be a valid 0x-prefixed private key"
}
]);
const envExists = fs.existsSync(localEnvPath);
const envContent = envExists ? fs.readFileSync(localEnvPath, "utf-8") : "";
const lines = envContent.split("\n").filter(Boolean);
const updatedLines = lines.filter((line) => !line.startsWith("PRIVATE_KEY="));
updatedLines.push(`PRIVATE_KEY=${privateKey}`);
fs.writeFileSync(localEnvPath, updatedLines.join("\n"), "utf-8");
Logger.info(
// eslint-disable-next-line prettier/prettier
`Environment variable PRIVATE_KEY set successfully ${localEnvPath}`
);
}
async function batchPayAction(cmd) {
const privateKey = process.env.PRIVATE_KEY;
const baseURL = process.env.RESOURCE_SERVER_URL;
const endpointPath = process.env.BATCH_ENDPOINT_PATH;
if (!baseURL || !privateKey || !endpointPath) {
Logger.error("Missing required environment variables");
process.exit(1);
}
const { identity, receivers } = cmd;
const allowedIdentities = ["twitter", "farcaster"];
if (!allowedIdentities.includes(identity.toLowerCase())) {
Logger.error("receiver_identity must be either 'twitter' or 'farcaster'");
process.exit(1);
}
let parsedReceivers;
try {
parsedReceivers = JSON.parse(receivers);
if (!Array.isArray(parsedReceivers)) {
throw new Error("Receivers must be an array");
}
} catch (e) {
Logger.error(
// eslint-disable-next-line prettier/prettier
`receivers must be a valid JSON array, e.g. '[{"username":"jrsarath","name":"Sarath Singh","amount":0.5}]'`
);
process.exit(1);
}
const account = privateKeyToAccount(privateKey);
const api = withPaymentInterceptor(axios.create({ baseURL }), account);
try {
const response = await api.post(endpointPath, {
currency: "USDC",
type: "social-network",
sender_username: "snackmoney-agent-x402",
receiver_identity: identity,
receivers: parsedReceivers
});
Logger.log("response", response.data);
const paymentResponse = decodeXPaymentResponse(
// eslint-disable-next-line prettier/prettier
response.headers["x-payment-response"]
);
Logger.log(paymentResponse);
} catch (error) {
Logger.error("error", error);
}
}
async function createRewardAction(cmd) {
const privateKey = process.env.PRIVATE_KEY;
const baseURL = process.env.RESOURCE_SERVER_URL;
const endpointPath = process.env.CREATE_ENDPOINT_PATH;
if (!baseURL || !privateKey || !endpointPath) {
Logger.error("Missing required environment variables");
process.exit(1);
}
const { platform, contentId, budget } = cmd;
const allowedPlatforms = ["twitter", "farcaster"];
if (!allowedPlatforms.includes(platform.toLowerCase())) {
Logger.error("platform must be either 'twitter' or 'farcaster'");
process.exit(1);
}
const parsedBudget = parseFloat(budget);
if (isNaN(parsedBudget)) {
Logger.error("budget must be a valid number, e.g., 0.01");
process.exit(1);
}
const account = privateKeyToAccount(privateKey);
const api = withPaymentInterceptor(axios.create({ baseURL }), account);
try {
const response = await api.post(endpointPath, {
budget: parsedBudget,
platform,
content_id: contentId
});
Logger.log("response", JSON.stringify(response.data, null, 2));
} catch (error) {
Logger.error("error", error);
}
}
async function confirmRewardAction(cmd) {
const privateKey = process.env.PRIVATE_KEY;
const baseURL = process.env.RESOURCE_SERVER_URL;
const endpointPath = process.env.CONFIRM_ENDPOINT_PATH;
if (!baseURL || !privateKey || !endpointPath) {
Logger.error("Missing required environment variables");
process.exit(1);
}
const { orderId } = cmd;
const account = privateKeyToAccount(privateKey);
const api = withPaymentInterceptor(axios.create({ baseURL }), account);
try {
const response = await api.post(`${endpointPath}/${orderId}`);
Logger.log("response", JSON.stringify(response.data, null, 2));
} catch (error) {
Logger.error("error", error);
}
}
// src/helper/math-diff.ts
function matchTextScore(text, pattern) {
let score = 0;
const textLength = text.length;
const patternLength = pattern.length;
let i = 0;
let j = 0;
while (i < textLength && j < patternLength) {
if (text[i] === pattern[j]) {
score++;
j++;
}
i++;
}
return score;
}
function findMostMatchText(list, pattern) {
let maxScore = 0;
let result = "";
for (const text of list) {
const score = matchTextScore(text, pattern);
if (score > maxScore) {
maxScore = score;
result = text;
}
}
return result !== "" ? result : null;
}
// src/index.ts
var __filename2 = fileURLToPath(import.meta.url);
var __dirname2 = path.dirname(__filename2);
var localEnvPath2 = path.resolve(__dirname2, "../.env");
dotenv.config({ path: localEnvPath2 });
var commandList = [
"pay",
"batch-pay",
"confirm-reward-distribution",
"create-reward-distribution",
"env"
];
var snackmoney = new Command();
snackmoney.name(`${package_default.name}`).usage("[command]").description(`${package_default.name} - ${package_default.description} - v${package_default.version}`).version(package_default.version, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help for command").action(async (_, command) => {
let isArgs = false;
if (command) {
const args = command.args?.[0];
if (args && !commandList.includes(args)) {
isArgs = true;
const matchCommand = findMostMatchText(commandList, args);
if (matchCommand) {
Logger.error(
// eslint-disable-next-line prettier/prettier
`Unknown command '${args}', Did you mean '${chalk.underline(matchCommand)}'?`
);
} else {
Logger.error(`Unknown command '${args}'`);
}
}
}
if (!isArgs) {
Logger.log("snackmoney --help");
}
process.exit(0);
});
snackmoney.command("env").description("Set your PRIVATE_KEY to .env file securely").action(envAction);
snackmoney.command("pay").description("Send USDC to a single user").requiredOption(
"-i --identity <identity>",
// eslint-disable-next-line prettier/prettier
"Identity platform: twitter or farcaster"
).requiredOption("-u --username <username>", "Receiver username").requiredOption("-a --amount <amount>", "Amount in USDC").action(payAction);
snackmoney.command("batch-pay").description("Send USDC to multiple recipients").requiredOption(
"-i --identity <identity>",
// eslint-disable-next-line prettier/prettier
"Identity platform: twitter or farcaster"
).requiredOption("-r --receivers <json>", "Receivers JSON array string").action(batchPayAction);
snackmoney.command("create-reward-distribution").description("Create a reward distribution order").requiredOption("-b --budget <budget>", "USDC budget").requiredOption("-p --platform <platform>", "Platform: twitter or farcaster").requiredOption("-c --content-id <contentId>", "Platform content ID").action(createRewardAction);
snackmoney.command("confirm-reward-distribution").description("Confirm reward distribution order").requiredOption(
"-o --order-id <orderId>",
// eslint-disable-next-line prettier/prettier
"Order ID to confirm and distribute"
).action(confirmRewardAction);
snackmoney.parse();
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map