@beeperbot/sdk 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/core/index.cjs +542 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +376 -0
- package/dist/core/index.d.ts +376 -0
- package/dist/core/index.js +531 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +615 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -56
- package/dist/index.d.ts +70 -56
- package/dist/index.js +606 -7
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -908,8 +908,16 @@ var ENDPOINTS = {
|
|
|
908
908
|
PREVIEW: "/agent/preview",
|
|
909
909
|
BULK_INTENT: "/agent/bulk-intent"
|
|
910
910
|
};
|
|
911
|
-
var AgentClient = class {
|
|
911
|
+
var AgentClient = class _AgentClient {
|
|
912
912
|
http;
|
|
913
|
+
static CHAIN_ID_MAP = {
|
|
914
|
+
base: 8453,
|
|
915
|
+
ethereum: 1,
|
|
916
|
+
mainnet: 1,
|
|
917
|
+
arbitrum: 42161,
|
|
918
|
+
polygon: 137,
|
|
919
|
+
optimism: 10
|
|
920
|
+
};
|
|
913
921
|
constructor(config) {
|
|
914
922
|
if (!config.apiKey) {
|
|
915
923
|
throw BeeperError.validation("API key is required");
|
|
@@ -924,6 +932,62 @@ var AgentClient = class {
|
|
|
924
932
|
});
|
|
925
933
|
this.http = new HttpClient(httpConfig);
|
|
926
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Normalize filters for agent endpoints (accepts flexible shapes from other builders)
|
|
937
|
+
*/
|
|
938
|
+
normalizeFilters(filters) {
|
|
939
|
+
const normalized = { ...filters };
|
|
940
|
+
const stripEmptyArray = (key) => {
|
|
941
|
+
const val = normalized[key];
|
|
942
|
+
if (Array.isArray(val) && val.length === 0) {
|
|
943
|
+
delete normalized[key];
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
const stripZero = (key) => {
|
|
947
|
+
const val = normalized[key];
|
|
948
|
+
if (typeof val === "number" && val === 0) {
|
|
949
|
+
delete normalized[key];
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
if (normalized.platform === "all") delete normalized.platform;
|
|
953
|
+
if (normalized.spamLabel === "all") delete normalized.spamLabel;
|
|
954
|
+
stripZero("minFollowers");
|
|
955
|
+
stripZero("maxFollowers");
|
|
956
|
+
stripZero("activeInLastDays");
|
|
957
|
+
stripZero("minBatteryPercentage");
|
|
958
|
+
stripZero("hasRechargedInLastDays");
|
|
959
|
+
stripZero("maxAttentionPriceUsd");
|
|
960
|
+
if (normalized.neynarScoreMin === 0) delete normalized.neynarScoreMin;
|
|
961
|
+
if (normalized.neynarScoreMax === 1) delete normalized.neynarScoreMax;
|
|
962
|
+
if (normalized.quotientScoreMin === 0) delete normalized.quotientScoreMin;
|
|
963
|
+
if (normalized.quotientScoreMax === 1) delete normalized.quotientScoreMax;
|
|
964
|
+
if (Array.isArray(normalized.countries)) {
|
|
965
|
+
normalized.countries = normalized.countries.map((c) => typeof c === "string" ? c : c?.code).filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim().toUpperCase());
|
|
966
|
+
}
|
|
967
|
+
stripEmptyArray("countries");
|
|
968
|
+
if (Array.isArray(normalized.tokenHolders)) {
|
|
969
|
+
normalized.tokenHolders = normalized.tokenHolders.map((holder) => {
|
|
970
|
+
if (!holder) return null;
|
|
971
|
+
if ("tokenAddress" in holder && "chainId" in holder) return holder;
|
|
972
|
+
const contractAddress = holder.contractAddress;
|
|
973
|
+
const chain = holder.chain;
|
|
974
|
+
const minBalance = holder.minBalance;
|
|
975
|
+
if (!contractAddress || chain == null) return null;
|
|
976
|
+
const chainId = typeof chain === "number" ? chain : _AgentClient.CHAIN_ID_MAP[String(chain).toLowerCase()] ?? Number(chain);
|
|
977
|
+
if (!Number.isFinite(chainId)) return null;
|
|
978
|
+
return {
|
|
979
|
+
tokenAddress: contractAddress,
|
|
980
|
+
chainId,
|
|
981
|
+
...minBalance ? { minBalance } : {}
|
|
982
|
+
};
|
|
983
|
+
}).filter((holder) => !!holder);
|
|
984
|
+
}
|
|
985
|
+
stripEmptyArray("tokenHolders");
|
|
986
|
+
stripEmptyArray("signalTokens");
|
|
987
|
+
stripEmptyArray("fids");
|
|
988
|
+
stripEmptyArray("userIds");
|
|
989
|
+
return normalized;
|
|
990
|
+
}
|
|
927
991
|
/**
|
|
928
992
|
* Look up a user by username, FID, or wallet address
|
|
929
993
|
*
|
|
@@ -1054,7 +1118,7 @@ var AgentClient = class {
|
|
|
1054
1118
|
const response = await this.http.post(
|
|
1055
1119
|
ENDPOINTS.ESTIMATE,
|
|
1056
1120
|
{
|
|
1057
|
-
filters: input.filters,
|
|
1121
|
+
filters: this.normalizeFilters(input.filters),
|
|
1058
1122
|
budgetUsd: budgetStr,
|
|
1059
1123
|
message: input.message
|
|
1060
1124
|
}
|
|
@@ -1084,7 +1148,7 @@ var AgentClient = class {
|
|
|
1084
1148
|
const response = await this.http.post(
|
|
1085
1149
|
ENDPOINTS.PREVIEW,
|
|
1086
1150
|
{
|
|
1087
|
-
filters: input.filters,
|
|
1151
|
+
filters: this.normalizeFilters(input.filters),
|
|
1088
1152
|
limit
|
|
1089
1153
|
}
|
|
1090
1154
|
);
|
|
@@ -1116,7 +1180,7 @@ var AgentClient = class {
|
|
|
1116
1180
|
const response = await this.http.post(
|
|
1117
1181
|
ENDPOINTS.BULK_INTENT,
|
|
1118
1182
|
{
|
|
1119
|
-
filters: input.filters,
|
|
1183
|
+
filters: this.normalizeFilters(input.filters),
|
|
1120
1184
|
budgetUsd: budgetStr,
|
|
1121
1185
|
message: input.message,
|
|
1122
1186
|
chainId: input.chainId ?? 8453
|
|
@@ -1160,6 +1224,530 @@ var AgentClient = class {
|
|
|
1160
1224
|
}
|
|
1161
1225
|
};
|
|
1162
1226
|
|
|
1227
|
+
// src/core/agent/errors.ts
|
|
1228
|
+
var AgentCoreError = class _AgentCoreError extends Error {
|
|
1229
|
+
code;
|
|
1230
|
+
constructor(code, message) {
|
|
1231
|
+
super(message);
|
|
1232
|
+
this.name = "AgentCoreError";
|
|
1233
|
+
this.code = code;
|
|
1234
|
+
}
|
|
1235
|
+
static validation(message) {
|
|
1236
|
+
return new _AgentCoreError("VALIDATION_ERROR", message);
|
|
1237
|
+
}
|
|
1238
|
+
static notFound(message) {
|
|
1239
|
+
return new _AgentCoreError("NOT_FOUND", message);
|
|
1240
|
+
}
|
|
1241
|
+
static unsupportedChain(chainId) {
|
|
1242
|
+
return new _AgentCoreError("UNSUPPORTED_CHAIN", `Unsupported chain: ${chainId}`);
|
|
1243
|
+
}
|
|
1244
|
+
static noRecipients(message) {
|
|
1245
|
+
return new _AgentCoreError("NO_RECIPIENTS", message);
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
// src/core/agent/filters.ts
|
|
1250
|
+
var CHAIN_ID_MAP = {
|
|
1251
|
+
base: 8453,
|
|
1252
|
+
ethereum: 1,
|
|
1253
|
+
mainnet: 1,
|
|
1254
|
+
arbitrum: 42161,
|
|
1255
|
+
polygon: 137,
|
|
1256
|
+
optimism: 10
|
|
1257
|
+
};
|
|
1258
|
+
function hasSubstantiveFilters(filters) {
|
|
1259
|
+
return !!(filters.platform || filters.minFollowers || filters.maxFollowers || filters.fids?.length || filters.userIds?.length || filters.activeInLastDays || filters.countries?.length || filters.neynarScoreMin || filters.tokenHolders?.length || filters.maxAttentionPriceUsd || filters.hasBaseWallet || filters.spamLabel);
|
|
1260
|
+
}
|
|
1261
|
+
function normalizeAgentFilters(filters) {
|
|
1262
|
+
const normalized = { ...filters };
|
|
1263
|
+
const defaultsApplied = [];
|
|
1264
|
+
const stripEmptyArray = (key) => {
|
|
1265
|
+
const value = normalized[key];
|
|
1266
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
1267
|
+
delete normalized[key];
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
const stripZero = (key) => {
|
|
1271
|
+
const value = normalized[key];
|
|
1272
|
+
if (typeof value === "number" && value === 0) {
|
|
1273
|
+
delete normalized[key];
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
if (normalized.platform === "all") delete normalized.platform;
|
|
1277
|
+
if (normalized.spamLabel === "all") delete normalized.spamLabel;
|
|
1278
|
+
stripZero("minFollowers");
|
|
1279
|
+
stripZero("maxFollowers");
|
|
1280
|
+
stripZero("activeInLastDays");
|
|
1281
|
+
stripZero("minBatteryPercentage");
|
|
1282
|
+
stripZero("hasRechargedInLastDays");
|
|
1283
|
+
stripZero("maxAttentionPriceUsd");
|
|
1284
|
+
stripEmptyArray("signalTokens");
|
|
1285
|
+
if (Array.isArray(normalized.tokenHolders)) {
|
|
1286
|
+
normalized.tokenHolders = normalized.tokenHolders.map((holder) => {
|
|
1287
|
+
if (!holder) return null;
|
|
1288
|
+
if ("tokenAddress" in holder && "chainId" in holder) return holder;
|
|
1289
|
+
const loose = holder;
|
|
1290
|
+
if (!loose.contractAddress || loose.chain == null) return null;
|
|
1291
|
+
const chainId = typeof loose.chain === "number" ? loose.chain : CHAIN_ID_MAP[String(loose.chain).toLowerCase()] ?? Number(loose.chain);
|
|
1292
|
+
if (!Number.isFinite(chainId)) return null;
|
|
1293
|
+
return {
|
|
1294
|
+
tokenAddress: loose.contractAddress,
|
|
1295
|
+
chainId,
|
|
1296
|
+
...loose.minBalance ? { minBalance: loose.minBalance } : {}
|
|
1297
|
+
};
|
|
1298
|
+
}).filter((holder) => !!holder);
|
|
1299
|
+
}
|
|
1300
|
+
stripEmptyArray("tokenHolders");
|
|
1301
|
+
if (Array.isArray(normalized.countries)) {
|
|
1302
|
+
normalized.countries = normalized.countries.map((country) => {
|
|
1303
|
+
if (typeof country === "string") return country;
|
|
1304
|
+
return country?.code;
|
|
1305
|
+
}).filter((country) => typeof country === "string" && country.length > 0).map((country) => country.toUpperCase());
|
|
1306
|
+
}
|
|
1307
|
+
stripEmptyArray("countries");
|
|
1308
|
+
stripEmptyArray("fids");
|
|
1309
|
+
stripEmptyArray("userIds");
|
|
1310
|
+
if (normalized.neynarScoreMin === 0) delete normalized.neynarScoreMin;
|
|
1311
|
+
if (normalized.neynarScoreMax === 1) delete normalized.neynarScoreMax;
|
|
1312
|
+
if (normalized.quotientScoreMin === 0) delete normalized.quotientScoreMin;
|
|
1313
|
+
if (normalized.quotientScoreMax === 1) delete normalized.quotientScoreMax;
|
|
1314
|
+
if (!hasSubstantiveFilters(normalized)) {
|
|
1315
|
+
normalized.activeInLastDays = 30;
|
|
1316
|
+
normalized.spamLabel = "not_spam_only";
|
|
1317
|
+
defaultsApplied.push("activeInLastDays=30", "spamLabel=not_spam_only");
|
|
1318
|
+
}
|
|
1319
|
+
return { filters: normalized, defaultsApplied };
|
|
1320
|
+
}
|
|
1321
|
+
function getOrderByOrDefault(filters) {
|
|
1322
|
+
return filters.orderBy ?? "attention_price_asc";
|
|
1323
|
+
}
|
|
1324
|
+
function buildAgentQueryPlan(filters) {
|
|
1325
|
+
const normalized = normalizeAgentFilters(filters);
|
|
1326
|
+
return {
|
|
1327
|
+
inputFilters: { ...filters },
|
|
1328
|
+
filters: normalized.filters,
|
|
1329
|
+
orderBy: getOrderByOrDefault(normalized.filters),
|
|
1330
|
+
defaultsApplied: normalized.defaultsApplied
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// src/core/agent/costing.ts
|
|
1335
|
+
var DEFAULT_MICRO_USD_PER_USD = 1e6;
|
|
1336
|
+
var DEFAULT_PLATFORM_FEE_RATE = 0.1;
|
|
1337
|
+
var GOAL_BASELINES = {
|
|
1338
|
+
max_reach: { payout: 100, bonus: 0 },
|
|
1339
|
+
lil_mission: { payout: 80, bonus: 20 },
|
|
1340
|
+
hard_mission: { payout: 50, bonus: 50 }
|
|
1341
|
+
};
|
|
1342
|
+
var SPLIT_ADJUSTMENTS = {
|
|
1343
|
+
balanced: 0,
|
|
1344
|
+
more_reach: 10,
|
|
1345
|
+
more_action: -10
|
|
1346
|
+
};
|
|
1347
|
+
function computeBudgetSplit(totalUsd, goalType = "max_reach", splitPreset = "balanced", platformFeeRate = DEFAULT_PLATFORM_FEE_RATE) {
|
|
1348
|
+
const baseline = GOAL_BASELINES[goalType];
|
|
1349
|
+
const adjustment = SPLIT_ADJUSTMENTS[splitPreset];
|
|
1350
|
+
let payoutPercent = Math.max(0, Math.min(100, baseline.payout + adjustment));
|
|
1351
|
+
let bonusPercent = Math.max(0, Math.min(100, baseline.bonus - adjustment));
|
|
1352
|
+
const sum = payoutPercent + bonusPercent;
|
|
1353
|
+
if (sum !== 100) {
|
|
1354
|
+
payoutPercent = payoutPercent / sum * 100;
|
|
1355
|
+
bonusPercent = bonusPercent / sum * 100;
|
|
1356
|
+
}
|
|
1357
|
+
const payoutUsd = roundToCents(totalUsd * payoutPercent / 100);
|
|
1358
|
+
const bonusUsd = roundToCents(totalUsd * bonusPercent / 100);
|
|
1359
|
+
const platformFeeUsd = roundToCents(totalUsd * platformFeeRate);
|
|
1360
|
+
const userPaysUsd = roundToCents(totalUsd + platformFeeUsd);
|
|
1361
|
+
return {
|
|
1362
|
+
totalUsd,
|
|
1363
|
+
payoutUsd,
|
|
1364
|
+
bonusUsd,
|
|
1365
|
+
platformFeeUsd,
|
|
1366
|
+
userPaysUsd,
|
|
1367
|
+
goalType,
|
|
1368
|
+
splitPreset,
|
|
1369
|
+
payoutPercent: Math.round(payoutPercent),
|
|
1370
|
+
bonusPercent: Math.round(bonusPercent)
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function roundToCents(value) {
|
|
1374
|
+
return Math.round(value * 100) / 100;
|
|
1375
|
+
}
|
|
1376
|
+
function parseUsd(value) {
|
|
1377
|
+
return parseFloat(value.replace(/^\$/, "").trim());
|
|
1378
|
+
}
|
|
1379
|
+
function usdToMicroUsd(usd, microUsdPerUsd = DEFAULT_MICRO_USD_PER_USD) {
|
|
1380
|
+
return Math.round(usd * microUsdPerUsd);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// src/core/agent/engine.ts
|
|
1384
|
+
var DEFAULT_CHAIN_INFO = {
|
|
1385
|
+
8453: { name: "Base", usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" }
|
|
1386
|
+
};
|
|
1387
|
+
var AgentCore = class {
|
|
1388
|
+
adapter;
|
|
1389
|
+
defaultChainId;
|
|
1390
|
+
platformFeeRate;
|
|
1391
|
+
estimateMaxUsers;
|
|
1392
|
+
bulkMaxUsers;
|
|
1393
|
+
microUsdPerUsd;
|
|
1394
|
+
chainInfo;
|
|
1395
|
+
constructor(adapter, config = {}) {
|
|
1396
|
+
this.adapter = adapter;
|
|
1397
|
+
this.defaultChainId = config.defaultChainId ?? 8453;
|
|
1398
|
+
this.platformFeeRate = config.platformFeeRate ?? DEFAULT_PLATFORM_FEE_RATE;
|
|
1399
|
+
this.estimateMaxUsers = config.estimateMaxUsers ?? 1e4;
|
|
1400
|
+
this.bulkMaxUsers = config.bulkMaxUsers ?? 1e4;
|
|
1401
|
+
this.microUsdPerUsd = config.microUsdPerUsd ?? DEFAULT_MICRO_USD_PER_USD;
|
|
1402
|
+
this.chainInfo = config.chainInfo ?? DEFAULT_CHAIN_INFO;
|
|
1403
|
+
}
|
|
1404
|
+
buildQueryPlan(filters) {
|
|
1405
|
+
return buildAgentQueryPlan(filters);
|
|
1406
|
+
}
|
|
1407
|
+
normalizeFilters(filters) {
|
|
1408
|
+
return normalizeAgentFilters(filters);
|
|
1409
|
+
}
|
|
1410
|
+
computeBudgetSplit(totalUsd, goalType = "max_reach", splitPreset = "balanced") {
|
|
1411
|
+
return computeBudgetSplit(totalUsd, goalType, splitPreset, this.platformFeeRate);
|
|
1412
|
+
}
|
|
1413
|
+
async lookup(query) {
|
|
1414
|
+
const user = await this.adapter.resolveUser(query);
|
|
1415
|
+
if (!user) return null;
|
|
1416
|
+
const fid = user.fid ?? await this.adapter.getFidForUser(user.id) ?? void 0;
|
|
1417
|
+
const walletAddress = user.walletAddress ?? await this.adapter.getPrimaryWalletAddress(user.id) ?? void 0;
|
|
1418
|
+
return {
|
|
1419
|
+
id: user.id,
|
|
1420
|
+
username: user.username,
|
|
1421
|
+
platform: user.platform,
|
|
1422
|
+
attentionPriceUsd: (Number(user.attentionPriceMicroUsd) / this.microUsdPerUsd).toFixed(2),
|
|
1423
|
+
...fid !== void 0 ? { fid } : {},
|
|
1424
|
+
...user.displayName ? { displayName: user.displayName } : {},
|
|
1425
|
+
...user.pfpUrl ? { pfpUrl: user.pfpUrl } : {},
|
|
1426
|
+
...walletAddress ? { walletAddress } : {},
|
|
1427
|
+
...user.followerCount !== void 0 ? { followerCount: user.followerCount } : {}
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
async getPrice(query) {
|
|
1431
|
+
const user = await this.lookup(query);
|
|
1432
|
+
if (!user) return null;
|
|
1433
|
+
const priceUsdNum = parseFloat(user.attentionPriceUsd);
|
|
1434
|
+
return {
|
|
1435
|
+
userId: user.id,
|
|
1436
|
+
username: user.username,
|
|
1437
|
+
priceUsd: user.attentionPriceUsd,
|
|
1438
|
+
priceUsdc: usdToMicroUsd(priceUsdNum, this.microUsdPerUsd).toString()
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
async prepareBeep(params) {
|
|
1442
|
+
const user = await this.lookup(params.to);
|
|
1443
|
+
if (!user) {
|
|
1444
|
+
return { error: "user_not_found", message: `User not found: ${params.to}` };
|
|
1445
|
+
}
|
|
1446
|
+
if (!user.walletAddress) {
|
|
1447
|
+
return { error: "no_wallet", message: `@${user.username} has no connected wallet` };
|
|
1448
|
+
}
|
|
1449
|
+
const baseAmount = parseUsd(params.amountUsd);
|
|
1450
|
+
if (Number.isNaN(baseAmount) || baseAmount <= 0) {
|
|
1451
|
+
return { error: "invalid_amount", message: "Amount must be a positive number" };
|
|
1452
|
+
}
|
|
1453
|
+
const attentionPrice = parseFloat(user.attentionPriceUsd);
|
|
1454
|
+
if (baseAmount < attentionPrice) {
|
|
1455
|
+
return {
|
|
1456
|
+
error: "below_minimum",
|
|
1457
|
+
message: `Amount $${baseAmount.toFixed(2)} is below @${user.username}'s minimum beep price of $${attentionPrice.toFixed(2)}.`,
|
|
1458
|
+
minimumUsd: user.attentionPriceUsd,
|
|
1459
|
+
requestedUsd: params.amountUsd
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
const chainId = params.chainId ?? this.defaultChainId;
|
|
1463
|
+
const chain = this.chainInfo[chainId];
|
|
1464
|
+
if (!chain) {
|
|
1465
|
+
return { error: "unsupported_chain", message: `Chain ${chainId} is not supported` };
|
|
1466
|
+
}
|
|
1467
|
+
const goalType = params.goalType ?? (params.bonusConfig ? "lil_mission" : "max_reach");
|
|
1468
|
+
const hasMissions = goalType !== "max_reach";
|
|
1469
|
+
const splitPreset = params.splitPreset ?? "balanced";
|
|
1470
|
+
const split = hasMissions ? this.computeBudgetSplit(baseAmount, goalType, splitPreset) : void 0;
|
|
1471
|
+
const platformFeeUsd = Math.round(baseAmount * this.platformFeeRate * 100) / 100;
|
|
1472
|
+
const totalAmountUsd = baseAmount + platformFeeUsd;
|
|
1473
|
+
const result = {
|
|
1474
|
+
type: "confirmation",
|
|
1475
|
+
recipient: {
|
|
1476
|
+
username: user.username,
|
|
1477
|
+
walletAddress: user.walletAddress,
|
|
1478
|
+
...user.displayName ? { displayName: user.displayName } : {},
|
|
1479
|
+
...user.pfpUrl ? { pfpUrl: user.pfpUrl } : {},
|
|
1480
|
+
...user.fid !== void 0 ? { fid: user.fid } : {}
|
|
1481
|
+
},
|
|
1482
|
+
message: params.message,
|
|
1483
|
+
baseAmountUsd: baseAmount.toFixed(2),
|
|
1484
|
+
platformFeeUsd: platformFeeUsd.toFixed(2),
|
|
1485
|
+
totalAmountUsd: totalAmountUsd.toFixed(2),
|
|
1486
|
+
feePercentage: this.platformFeeRate * 100,
|
|
1487
|
+
chainId,
|
|
1488
|
+
chainName: chain.name,
|
|
1489
|
+
attentionPriceUsd: user.attentionPriceUsd
|
|
1490
|
+
};
|
|
1491
|
+
if (hasMissions && split) {
|
|
1492
|
+
result.hasMissions = true;
|
|
1493
|
+
result.budgetSplit = split;
|
|
1494
|
+
if (params.bonusConfig) {
|
|
1495
|
+
result.bonusConfig = params.bonusConfig;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return result;
|
|
1499
|
+
}
|
|
1500
|
+
async createIntent(params) {
|
|
1501
|
+
const user = await this.lookup(params.to);
|
|
1502
|
+
if (!user || !user.walletAddress) return null;
|
|
1503
|
+
const baseAmountUsd = parseUsd(params.amountUsd);
|
|
1504
|
+
if (Number.isNaN(baseAmountUsd) || baseAmountUsd <= 0) {
|
|
1505
|
+
throw AgentCoreError.validation("Invalid amount");
|
|
1506
|
+
}
|
|
1507
|
+
const chainId = params.chainId ?? this.defaultChainId;
|
|
1508
|
+
const chain = this.chainInfo[chainId];
|
|
1509
|
+
if (!chain) throw AgentCoreError.unsupportedChain(chainId);
|
|
1510
|
+
const platformFeeUsd = Math.round(baseAmountUsd * this.platformFeeRate * 100) / 100;
|
|
1511
|
+
const totalAmountUsd = baseAmountUsd + platformFeeUsd;
|
|
1512
|
+
const amountUsdc = usdToMicroUsd(totalAmountUsd, this.microUsdPerUsd).toString();
|
|
1513
|
+
const intentId = `intent_${crypto.randomUUID()}`;
|
|
1514
|
+
const shortAddress = `${user.walletAddress.slice(0, 6)}...${user.walletAddress.slice(-4)}`;
|
|
1515
|
+
const message = params.message ?? "";
|
|
1516
|
+
return {
|
|
1517
|
+
intentId,
|
|
1518
|
+
recipient: user.walletAddress,
|
|
1519
|
+
amount: amountUsdc,
|
|
1520
|
+
amountFormatted: `${totalAmountUsd.toFixed(2)} USDC`,
|
|
1521
|
+
tokenAddress: chain.usdcAddress,
|
|
1522
|
+
tokenSymbol: "USDC",
|
|
1523
|
+
chainId,
|
|
1524
|
+
chainName: chain.name,
|
|
1525
|
+
message,
|
|
1526
|
+
recipientInfo: {
|
|
1527
|
+
username: user.username,
|
|
1528
|
+
...user.displayName ? { displayName: user.displayName } : {},
|
|
1529
|
+
...user.fid !== void 0 ? { fid: user.fid } : {}
|
|
1530
|
+
},
|
|
1531
|
+
instruction: `Send ${totalAmountUsd.toFixed(2)} USDC to ${shortAddress} on ${chain.name} (${baseAmountUsd.toFixed(2)} to @${user.username} + ${platformFeeUsd.toFixed(2)} platform fee)`,
|
|
1532
|
+
expiresAt: new Date(Date.now() + 60 * 60 * 1e3).toISOString(),
|
|
1533
|
+
baseAmountUsd: baseAmountUsd.toFixed(2),
|
|
1534
|
+
platformFeeUsd: platformFeeUsd.toFixed(2),
|
|
1535
|
+
totalAmountUsd: totalAmountUsd.toFixed(2),
|
|
1536
|
+
feePercentage: this.platformFeeRate * 100
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
async estimate(params) {
|
|
1540
|
+
const budgetUsd = parseUsd(params.budgetUsd);
|
|
1541
|
+
if (Number.isNaN(budgetUsd) || budgetUsd <= 0) {
|
|
1542
|
+
throw AgentCoreError.validation("Budget must be a positive number");
|
|
1543
|
+
}
|
|
1544
|
+
const plan = this.buildQueryPlan(params.filters);
|
|
1545
|
+
const candidates = await this.adapter.findUsers({
|
|
1546
|
+
filters: plan.filters,
|
|
1547
|
+
orderBy: plan.orderBy,
|
|
1548
|
+
limit: this.estimateMaxUsers
|
|
1549
|
+
});
|
|
1550
|
+
const budgetMicro = usdToMicroUsd(budgetUsd, this.microUsdPerUsd);
|
|
1551
|
+
let totalCost = 0;
|
|
1552
|
+
let recipientCount = 0;
|
|
1553
|
+
let minPrice = Number.POSITIVE_INFINITY;
|
|
1554
|
+
let maxPrice = 0;
|
|
1555
|
+
for (const candidate of candidates) {
|
|
1556
|
+
const priceMicro = Number(candidate.attentionPriceMicroUsd);
|
|
1557
|
+
if (totalCost + priceMicro > budgetMicro) break;
|
|
1558
|
+
totalCost += priceMicro;
|
|
1559
|
+
recipientCount += 1;
|
|
1560
|
+
if (priceMicro < minPrice) minPrice = priceMicro;
|
|
1561
|
+
if (priceMicro > maxPrice) maxPrice = priceMicro;
|
|
1562
|
+
}
|
|
1563
|
+
const avgPrice = recipientCount > 0 ? totalCost / recipientCount : 0;
|
|
1564
|
+
return {
|
|
1565
|
+
recipientCount,
|
|
1566
|
+
totalCostUsd: (totalCost / this.microUsdPerUsd).toFixed(2),
|
|
1567
|
+
avgPriceUsd: (avgPrice / this.microUsdPerUsd).toFixed(2),
|
|
1568
|
+
minPriceUsd: recipientCount > 0 ? (minPrice / this.microUsdPerUsd).toFixed(2) : "0.00",
|
|
1569
|
+
maxPriceUsd: recipientCount > 0 ? (maxPrice / this.microUsdPerUsd).toFixed(2) : "0.00",
|
|
1570
|
+
remainingBudgetUsd: ((budgetMicro - totalCost) / this.microUsdPerUsd).toFixed(2),
|
|
1571
|
+
budgetSufficient: recipientCount > 0,
|
|
1572
|
+
diagnostics: {
|
|
1573
|
+
candidateCount: candidates.length,
|
|
1574
|
+
budgetMicro,
|
|
1575
|
+
appliedDefaults: plan.defaultsApplied
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
async preview(params) {
|
|
1580
|
+
const limit = Math.min(Math.max(params.limit, 1), 20);
|
|
1581
|
+
const plan = this.buildQueryPlan(params.filters);
|
|
1582
|
+
const [users, totalCount] = await Promise.all([
|
|
1583
|
+
this.adapter.findUsers({
|
|
1584
|
+
filters: plan.filters,
|
|
1585
|
+
orderBy: plan.orderBy,
|
|
1586
|
+
limit,
|
|
1587
|
+
includeProfile: true,
|
|
1588
|
+
includeReputation: true
|
|
1589
|
+
}),
|
|
1590
|
+
this.adapter.countUsers(plan.filters)
|
|
1591
|
+
]);
|
|
1592
|
+
const userIds = users.map((user) => user.id);
|
|
1593
|
+
const fidMap = await this.adapter.getBulkFidsForUsers(userIds);
|
|
1594
|
+
return {
|
|
1595
|
+
users: users.map((user) => {
|
|
1596
|
+
const previewUser = {
|
|
1597
|
+
username: user.username,
|
|
1598
|
+
priceUsd: (Number(user.attentionPriceMicroUsd) / this.microUsdPerUsd).toFixed(2),
|
|
1599
|
+
...user.displayName ? { displayName: user.displayName } : {},
|
|
1600
|
+
...user.followerCount !== void 0 ? { followerCount: user.followerCount } : {},
|
|
1601
|
+
...user.neynarScore !== void 0 ? { neynarScore: user.neynarScore } : {},
|
|
1602
|
+
...user.pfpUrl ? { pfpUrl: user.pfpUrl } : {}
|
|
1603
|
+
};
|
|
1604
|
+
const fid = fidMap.get(user.id);
|
|
1605
|
+
if (fid !== void 0) {
|
|
1606
|
+
return { ...previewUser, fid };
|
|
1607
|
+
}
|
|
1608
|
+
return previewUser;
|
|
1609
|
+
}),
|
|
1610
|
+
totalCount,
|
|
1611
|
+
hasMore: totalCount > limit
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
async createBulkIntent(params) {
|
|
1615
|
+
const budgetUsd = parseUsd(params.budgetUsd);
|
|
1616
|
+
if (Number.isNaN(budgetUsd) || budgetUsd <= 0) {
|
|
1617
|
+
throw AgentCoreError.validation("Budget must be a positive number");
|
|
1618
|
+
}
|
|
1619
|
+
const chainId = params.chainId ?? this.defaultChainId;
|
|
1620
|
+
const chain = this.chainInfo[chainId];
|
|
1621
|
+
if (!chain) throw AgentCoreError.unsupportedChain(chainId);
|
|
1622
|
+
const plan = this.buildQueryPlan(params.filters);
|
|
1623
|
+
const users = await this.adapter.findUsers({
|
|
1624
|
+
filters: plan.filters,
|
|
1625
|
+
orderBy: "attention_price_asc",
|
|
1626
|
+
limit: this.bulkMaxUsers
|
|
1627
|
+
});
|
|
1628
|
+
const budgetMicro = usdToMicroUsd(budgetUsd, this.microUsdPerUsd);
|
|
1629
|
+
let totalAmountMicro = 0;
|
|
1630
|
+
const selectedUsers = [];
|
|
1631
|
+
for (const user of users) {
|
|
1632
|
+
const priceMicro = Number(user.attentionPriceMicroUsd);
|
|
1633
|
+
if (totalAmountMicro + priceMicro > budgetMicro) break;
|
|
1634
|
+
totalAmountMicro += priceMicro;
|
|
1635
|
+
selectedUsers.push({
|
|
1636
|
+
id: user.id,
|
|
1637
|
+
username: user.username,
|
|
1638
|
+
amountMicro: priceMicro
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
if (selectedUsers.length === 0) {
|
|
1642
|
+
throw AgentCoreError.noRecipients("No eligible recipients within budget");
|
|
1643
|
+
}
|
|
1644
|
+
const walletMap = await this.adapter.getBulkPrimaryWalletAddresses(selectedUsers.map((u) => u.id));
|
|
1645
|
+
const payments = selectedUsers.filter((user) => walletMap.has(user.id)).map((user) => ({
|
|
1646
|
+
recipient: walletMap.get(user.id),
|
|
1647
|
+
amount: user.amountMicro.toString(),
|
|
1648
|
+
username: user.username
|
|
1649
|
+
}));
|
|
1650
|
+
if (payments.length === 0) {
|
|
1651
|
+
throw AgentCoreError.noRecipients("No recipients with valid wallets found");
|
|
1652
|
+
}
|
|
1653
|
+
const totalAmount = payments.reduce((sum, payment) => sum + BigInt(payment.amount), 0n);
|
|
1654
|
+
const totalFormatted = (Number(totalAmount) / this.microUsdPerUsd).toFixed(2);
|
|
1655
|
+
return {
|
|
1656
|
+
bulkIntentId: `bulk_${crypto.randomUUID()}`,
|
|
1657
|
+
recipientCount: payments.length,
|
|
1658
|
+
totalAmount: totalAmount.toString(),
|
|
1659
|
+
totalAmountFormatted: `${totalFormatted} USDC`,
|
|
1660
|
+
payments,
|
|
1661
|
+
summary: `Send to ${payments.length} recipients for total ${totalFormatted} USDC on ${chain.name}`,
|
|
1662
|
+
chainId,
|
|
1663
|
+
chainName: chain.name,
|
|
1664
|
+
tokenAddress: chain.usdcAddress,
|
|
1665
|
+
tokenSymbol: "USDC",
|
|
1666
|
+
expiresAt: new Date(Date.now() + 60 * 60 * 1e3).toISOString()
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
async prepareCampaign(params) {
|
|
1670
|
+
const totalBudget = parseUsd(params.budgetUsd);
|
|
1671
|
+
if (Number.isNaN(totalBudget) || totalBudget <= 0) {
|
|
1672
|
+
throw AgentCoreError.validation("Budget must be a positive number");
|
|
1673
|
+
}
|
|
1674
|
+
const chainId = params.chainId ?? this.defaultChainId;
|
|
1675
|
+
const chain = this.chainInfo[chainId];
|
|
1676
|
+
if (!chain) throw AgentCoreError.unsupportedChain(chainId);
|
|
1677
|
+
const goalType = params.goalType ?? (params.bonusConfig ? "lil_mission" : "max_reach");
|
|
1678
|
+
const splitPreset = params.splitPreset ?? "balanced";
|
|
1679
|
+
const split = this.computeBudgetSplit(totalBudget, goalType, splitPreset);
|
|
1680
|
+
const payoutEstimate = await this.estimate({
|
|
1681
|
+
filters: params.filters,
|
|
1682
|
+
budgetUsd: split.payoutUsd.toFixed(2),
|
|
1683
|
+
...params.message ? { message: params.message } : {}
|
|
1684
|
+
});
|
|
1685
|
+
let missionSummary;
|
|
1686
|
+
if (params.bonusConfig) {
|
|
1687
|
+
const ctaLabels = params.bonusConfig.ctas.map((cta) => {
|
|
1688
|
+
switch (cta.type) {
|
|
1689
|
+
case "follow_profile":
|
|
1690
|
+
return `Follow @${cta.profileUsername || cta.profileFid}`;
|
|
1691
|
+
case "like_cast":
|
|
1692
|
+
return "Like a cast";
|
|
1693
|
+
case "recast":
|
|
1694
|
+
return "Recast";
|
|
1695
|
+
case "share_cast":
|
|
1696
|
+
return "Quote cast";
|
|
1697
|
+
case "visit_link":
|
|
1698
|
+
return `Visit ${cta.label || cta.url || "link"}`;
|
|
1699
|
+
case "quiz":
|
|
1700
|
+
return `Quiz (${cta.questions?.length || 0} questions)`;
|
|
1701
|
+
case "external_verify":
|
|
1702
|
+
return cta.description || "Custom quest";
|
|
1703
|
+
case "x_follow":
|
|
1704
|
+
return `Follow @${cta.handle} on X`;
|
|
1705
|
+
case "x_like":
|
|
1706
|
+
return "Like tweet on X";
|
|
1707
|
+
case "x_recast":
|
|
1708
|
+
return "Repost on X";
|
|
1709
|
+
default:
|
|
1710
|
+
return cta.type;
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
const distributionLabel = params.bonusConfig.type === "lottery" ? "BEEPERY (lottery)" : "FCFS (first come)";
|
|
1714
|
+
missionSummary = `${distributionLabel} \u2022 $${split.bonusUsd.toFixed(2)} pool \u2022 ${ctaLabels.join(", ")}`;
|
|
1715
|
+
}
|
|
1716
|
+
return {
|
|
1717
|
+
type: "campaign_confirmation",
|
|
1718
|
+
recipientCount: payoutEstimate.recipientCount,
|
|
1719
|
+
budgetSplit: split,
|
|
1720
|
+
payoutEstimate: {
|
|
1721
|
+
recipientCount: payoutEstimate.recipientCount,
|
|
1722
|
+
totalPayoutCostUsd: payoutEstimate.totalCostUsd,
|
|
1723
|
+
avgPriceUsd: payoutEstimate.avgPriceUsd
|
|
1724
|
+
},
|
|
1725
|
+
platformFeeUsd: split.platformFeeUsd.toFixed(2),
|
|
1726
|
+
userPaysUsd: split.userPaysUsd.toFixed(2),
|
|
1727
|
+
chainId,
|
|
1728
|
+
chainName: chain.name,
|
|
1729
|
+
...params.bonusConfig ? { bonusConfig: params.bonusConfig } : {},
|
|
1730
|
+
...missionSummary ? { missionSummary } : {},
|
|
1731
|
+
...params.message ? { message: params.message } : {}
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
async getBeepHistory(params) {
|
|
1735
|
+
return this.adapter.getBeepHistory(params);
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
function createAgentCore(adapter, config) {
|
|
1739
|
+
return new AgentCore(adapter, config);
|
|
1740
|
+
}
|
|
1741
|
+
function normalizeFilters(filters) {
|
|
1742
|
+
return normalizeAgentFilters(filters);
|
|
1743
|
+
}
|
|
1744
|
+
function buildQueryPlan(filters) {
|
|
1745
|
+
return buildAgentQueryPlan(filters);
|
|
1746
|
+
}
|
|
1747
|
+
function getDefaultOrderBy(filters) {
|
|
1748
|
+
return buildAgentQueryPlan(filters).orderBy;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1163
1751
|
// src/client/auth.ts
|
|
1164
1752
|
var API_KEY_PREFIXES = {
|
|
1165
1753
|
LIVE: "bpk_live_",
|
|
@@ -1198,6 +1786,7 @@ function maskApiKey(apiKey) {
|
|
|
1198
1786
|
}
|
|
1199
1787
|
|
|
1200
1788
|
// src/client/BeeperClient.ts
|
|
1789
|
+
var ALLOWED_REWARD_TYPES = ["guaranteed", "lottery", "fcfs"];
|
|
1201
1790
|
var QuoteSchema = zod.z.object({
|
|
1202
1791
|
id: zod.z.string(),
|
|
1203
1792
|
status: zod.z.enum([
|
|
@@ -1404,6 +1993,16 @@ var BeeperClient = class {
|
|
|
1404
1993
|
if (input.message.length > 1e3) {
|
|
1405
1994
|
throw BeeperError.validation("Message must be 1000 characters or less");
|
|
1406
1995
|
}
|
|
1996
|
+
if (input.rewardType !== void 0) {
|
|
1997
|
+
if (input.rewardType.trim().length === 0) {
|
|
1998
|
+
throw BeeperError.validation("rewardType must be a non-empty string");
|
|
1999
|
+
}
|
|
2000
|
+
if (!ALLOWED_REWARD_TYPES.includes(input.rewardType)) {
|
|
2001
|
+
throw BeeperError.validation(
|
|
2002
|
+
`rewardType must be one of: ${ALLOWED_REWARD_TYPES.join(", ")}`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
1407
2006
|
if (!input.recipientFids?.length && !input.filter) {
|
|
1408
2007
|
throw BeeperError.validation("Must provide either recipientFids or filter");
|
|
1409
2008
|
}
|
|
@@ -1422,6 +2021,7 @@ var BeeperClient = class {
|
|
|
1422
2021
|
filter: input.filter,
|
|
1423
2022
|
budgetUSD: input.budgetUSD,
|
|
1424
2023
|
rewardType: input.rewardType ?? "guaranteed",
|
|
2024
|
+
mode: "attention_marketplace",
|
|
1425
2025
|
memo: input.memo,
|
|
1426
2026
|
metadata: input.metadata,
|
|
1427
2027
|
ttlSeconds: opts?.ttlSeconds ?? 300
|
|
@@ -2785,11 +3385,10 @@ var FilterBuilder = class _FilterBuilder {
|
|
|
2785
3385
|
throw new Error("minBalance must be a numeric string (wei)");
|
|
2786
3386
|
}
|
|
2787
3387
|
}
|
|
2788
|
-
const chainNames = { 1: "ethereum", 8453: "base", 42161: "arbitrum", 10: "optimism", 137: "polygon" };
|
|
2789
3388
|
return new FilterExpression({
|
|
2790
3389
|
cachedTokenHolders: opts.map((o) => ({
|
|
2791
3390
|
contractAddress: o.tokenAddress,
|
|
2792
|
-
chain:
|
|
3391
|
+
chain: o.chainId === 8453 ? "base" : "ethereum",
|
|
2793
3392
|
tokenStandard: o.tokenStandard ?? "ERC20",
|
|
2794
3393
|
...o.minBalance !== void 0 ? { minBalance: o.minBalance } : {},
|
|
2795
3394
|
...o.tokenId !== void 0 ? { tokenId: o.tokenId } : {}
|
|
@@ -3242,6 +3841,8 @@ exports.API_BASE_URLS = API_BASE_URLS;
|
|
|
3242
3841
|
exports.API_KEY_PREFIXES = API_KEY_PREFIXES;
|
|
3243
3842
|
exports.ActiveInLastDaysFilterSchema = ActiveInLastDaysFilterSchema;
|
|
3244
3843
|
exports.AgentClient = AgentClient;
|
|
3844
|
+
exports.AgentCore = AgentCore;
|
|
3845
|
+
exports.AgentCoreError = AgentCoreError;
|
|
3245
3846
|
exports.ApiQuoteResponseSchema = ApiQuoteResponseSchema;
|
|
3246
3847
|
exports.ApiReceiptResponseSchema = ApiReceiptResponseSchema;
|
|
3247
3848
|
exports.BeeperClient = BeeperClient;
|
|
@@ -3335,6 +3936,10 @@ exports.TokenHolderFilterSchema = TokenHolderFilterSchema;
|
|
|
3335
3936
|
exports.TokenTypeSchema = TokenTypeSchema;
|
|
3336
3937
|
exports.TransferStatusSchema = TransferStatusSchema;
|
|
3337
3938
|
exports.VerifiedOnlyFilterSchema = VerifiedOnlyFilterSchema;
|
|
3939
|
+
exports.buildAgentCoreQueryPlan = buildQueryPlan;
|
|
3940
|
+
exports.buildAgentQueryPlan = buildAgentQueryPlan;
|
|
3941
|
+
exports.computeAgentBudgetSplit = computeBudgetSplit;
|
|
3942
|
+
exports.createAgentCore = createAgentCore;
|
|
3338
3943
|
exports.createHttpConfig = createHttpConfig;
|
|
3339
3944
|
exports.default = BeeperClient_default;
|
|
3340
3945
|
exports.describeFilters = describeFilters;
|
|
@@ -3342,12 +3947,16 @@ exports.generateDepositIdempotencyKey = generateDepositIdempotencyKey;
|
|
|
3342
3947
|
exports.generateExecuteIdempotencyKey = generateExecuteIdempotencyKey;
|
|
3343
3948
|
exports.generateFilterDocumentation = generateFilterDocumentation;
|
|
3344
3949
|
exports.generateIdempotencyKey = generateIdempotencyKey;
|
|
3950
|
+
exports.getAgentCoreDefaultOrderBy = getDefaultOrderBy;
|
|
3345
3951
|
exports.getAllFilterNames = getAllFilterNames;
|
|
3346
3952
|
exports.getApiKeyEnvironment = getApiKeyEnvironment;
|
|
3347
3953
|
exports.getFilterSchema = getFilterSchema;
|
|
3954
|
+
exports.getOrderByOrDefault = getOrderByOrDefault;
|
|
3348
3955
|
exports.isRetryableCode = isRetryableCode;
|
|
3349
3956
|
exports.isValidApiKeyFormat = isValidApiKeyFormat;
|
|
3350
3957
|
exports.maskApiKey = maskApiKey;
|
|
3958
|
+
exports.normalizeAgentCoreFilters = normalizeFilters;
|
|
3959
|
+
exports.normalizeAgentFilters = normalizeAgentFilters;
|
|
3351
3960
|
exports.parseDraft = parseDraft;
|
|
3352
3961
|
exports.parseDraftInput = parseDraftInput;
|
|
3353
3962
|
exports.parseExecuteResult = parseExecuteResult;
|