UNPKG

snackmoney-testing

Version:

A CLI tool for sending USDC payments on Twitter and Farcaster using x402.

417 lines (407 loc) 15.2 kB
#!/usr/bin/env node 'use strict'; var fs = require('fs'); var dotenv = require('dotenv'); var commander = require('commander'); var path2 = require('path'); var url = require('url'); var axios = require('axios'); var accounts = require('viem/accounts'); var x402Axios = require('x402-axios'); var inquirer = require('inquirer'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var fs__default = /*#__PURE__*/_interopDefault(fs); var dotenv__default = /*#__PURE__*/_interopDefault(dotenv); var path2__default = /*#__PURE__*/_interopDefault(path2); var axios__default = /*#__PURE__*/_interopDefault(axios); var inquirer__default = /*#__PURE__*/_interopDefault(inquirer); // node_modules/kleur/index.mjs var FORCE_COLOR; var NODE_DISABLE_COLORS; var NO_COLOR; var TERM; var isTTY = true; if (typeof process !== "undefined") { ({ FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM } = process.env || {}); isTTY = process.stdout && process.stdout.isTTY; } var $ = { enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== "dumb" && (FORCE_COLOR != null && FORCE_COLOR !== "0" || isTTY), // modifiers reset: init(0, 0), bold: init(1, 22), dim: init(2, 22), italic: init(3, 23), underline: init(4, 24), inverse: init(7, 27), hidden: init(8, 28), strikethrough: init(9, 29), // colors black: init(30, 39), red: init(31, 39), green: init(32, 39), yellow: init(33, 39), blue: init(34, 39), magenta: init(35, 39), cyan: init(36, 39), white: init(37, 39), gray: init(90, 39), grey: init(90, 39), // background colors bgBlack: init(40, 49), bgRed: init(41, 49), bgGreen: init(42, 49), bgYellow: init(43, 49), bgBlue: init(44, 49), bgMagenta: init(45, 49), bgCyan: init(46, 49), bgWhite: init(47, 49) }; function run(arr, str) { let i = 0, tmp, beg = "", end = ""; for (; i < arr.length; i++) { tmp = arr[i]; beg += tmp.open; end += tmp.close; if (!!~str.indexOf(tmp.close)) { str = str.replace(tmp.rgx, tmp.close + tmp.open); } } return beg + str + end; } function chain(has, keys) { let ctx = { has, keys }; ctx.reset = $.reset.bind(ctx); ctx.bold = $.bold.bind(ctx); ctx.dim = $.dim.bind(ctx); ctx.italic = $.italic.bind(ctx); ctx.underline = $.underline.bind(ctx); ctx.inverse = $.inverse.bind(ctx); ctx.hidden = $.hidden.bind(ctx); ctx.strikethrough = $.strikethrough.bind(ctx); ctx.black = $.black.bind(ctx); ctx.red = $.red.bind(ctx); ctx.green = $.green.bind(ctx); ctx.yellow = $.yellow.bind(ctx); ctx.blue = $.blue.bind(ctx); ctx.magenta = $.magenta.bind(ctx); ctx.cyan = $.cyan.bind(ctx); ctx.white = $.white.bind(ctx); ctx.gray = $.gray.bind(ctx); ctx.grey = $.grey.bind(ctx); ctx.bgBlack = $.bgBlack.bind(ctx); ctx.bgRed = $.bgRed.bind(ctx); ctx.bgGreen = $.bgGreen.bind(ctx); ctx.bgYellow = $.bgYellow.bind(ctx); ctx.bgBlue = $.bgBlue.bind(ctx); ctx.bgMagenta = $.bgMagenta.bind(ctx); ctx.bgCyan = $.bgCyan.bind(ctx); ctx.bgWhite = $.bgWhite.bind(ctx); return ctx; } function init(open, close) { let blk = { open: `\x1B[${open}m`, close: `\x1B[${close}m`, rgx: new RegExp(`\\x1b\\[${close}m`, "g") }; return function(txt) { if (this !== void 0 && this.has !== void 0) { !!~this.has.indexOf(open) || (this.has.push(open), this.keys.push(blk)); return txt === void 0 ? this : $.enabled ? run(this.keys, txt + "") : txt + ""; } return txt === void 0 ? chain([open], [blk]) : $.enabled ? run([blk], txt + "") : txt + ""; }; } var kleur_default = $; // package.json var package_default = { name: "snackmoney-testing", version: "0.0.5", description: "A CLI tool for sending USDC payments on Twitter and Farcaster using x402."}; // src/helper/logger.ts var logPrefix = kleur_default.bold(kleur_default.blue("Snack Money CLI:")); var Logger = class { constructor() { } static log(...args) { console.log(logPrefix, ...args); } static info(...args) { console.info(logPrefix, ...args.map((item) => kleur_default.blue(item))); } static success(...args) { console.info(logPrefix, ...args.map((item) => kleur_default.green(item))); } static warn(...args) { console.warn(logPrefix, ...args.map((item) => kleur_default.yellow(item))); } static error(...args) { console.error(logPrefix, ...args.map((item) => kleur_default.red(item))); } static grey(...args) { console.log(logPrefix, ...args.map((item) => kleur_default.gray(item))); } 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 = "https://api.snack.money"; const endpointPath = "/payments/pay"; if (!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 = accounts.privateKeyToAccount(privateKey); const api = x402Axios.withPaymentInterceptor(axios__default.default.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 = x402Axios.decodeXPaymentResponse( // eslint-disable-next-line prettier/prettier response.headers["x-payment-response"] ); Logger.log(paymentResponse); } catch (error) { Logger.error("error", error); } } var __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))); var __dirname$1 = path2__default.default.dirname(__filename$1); var localEnvPath = path2__default.default.resolve(__dirname$1, "../.env"); async function envAction() { const { privateKey } = await inquirer__default.default.prompt([ { name: "privateKey", type: "password", message: "Enter your Ethereum PRIVATE_KEY:", mask: "*", validate: (input) => { const isValid = /^0x[0-9a-fA-F]{64}$/.test(input); if (!isValid) { Logger.error("Must be a valid 0x-prefixed private key"); return "Invalid private key format."; } return true; } } ]); const envExists = fs__default.default.existsSync(localEnvPath); const envContent = envExists ? fs__default.default.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__default.default.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 = "https://api.snack.money"; const endpointPath = "/payments/batch-pay"; if (!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 = accounts.privateKeyToAccount(privateKey); const api = x402Axios.withPaymentInterceptor(axios__default.default.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 = x402Axios.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 = "https://api.snack.money"; const endpointPath = "/rewards/create-distribution"; if (!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 = accounts.privateKeyToAccount(privateKey); const api = x402Axios.withPaymentInterceptor(axios__default.default.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 = "https://api.snack.money"; const endpointPath = "/rewards/confirm-distribution"; if (!privateKey || !endpointPath) { Logger.error("Missing required environment variables"); process.exit(1); } const { orderId } = cmd; const account = accounts.privateKeyToAccount(privateKey); const api = x402Axios.withPaymentInterceptor(axios__default.default.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 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))); var __dirname2 = path2__default.default.dirname(__filename2); var localEnvPath2 = path2__default.default.resolve(__dirname2, "../.env"); var fallbackEnvPath = path2__default.default.resolve(process.cwd(), ".env"); var envPathToUse = fs__default.default.existsSync(localEnvPath2) ? localEnvPath2 : fs__default.default.existsSync(fallbackEnvPath) ? fallbackEnvPath : null; if (envPathToUse) { dotenv__default.default.config({ path: envPathToUse }); Logger.info(`Loaded .env from ${envPathToUse}`); } else { Logger.warn("\u26A0\uFE0F No .env file found, some commands may not work properly."); } var commandList = [ "pay", "batch-pay", "confirm-reward-distribution", "create-reward-distribution", "env" ]; var snackmoney = new commander.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 '${kleur_default.underline(matchCommand)}'?` ); } else { Logger.error(`Unknown command '${args}'`); } } } if (!isArgs) { Logger.info("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();