@elisym/sdk 0.10.0 → 0.10.2
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/dist/{assets-CMf-v55Z.d.cts → assets-C-nzSYD4.d.cts} +5 -1
- package/dist/{assets-CMf-v55Z.d.ts → assets-C-nzSYD4.d.ts} +5 -1
- package/dist/index.cjs +123 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -18
- package/dist/index.d.ts +23 -18
- package/dist/index.js +122 -166
- package/dist/index.js.map +1 -1
- package/dist/skills.cjs +3 -2
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -2
- package/dist/skills.js.map +1 -1
- package/package.json +1 -1
|
@@ -52,7 +52,11 @@ declare function resolveAssetFromPaymentRequest(request: {
|
|
|
52
52
|
* call-sites safe).
|
|
53
53
|
*/
|
|
54
54
|
declare function parseAssetAmount(asset: Asset, human: string): bigint;
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Format raw subunits back to `"<value> <SYMBOL>"`. Trailing zeros and a bare
|
|
57
|
+
* trailing dot are stripped, so 0.01 USDC renders as `"0.01 USDC"` rather than
|
|
58
|
+
* `"0.010000 USDC"`.
|
|
59
|
+
*/
|
|
56
60
|
declare function formatAssetAmount(asset: Asset, raw: bigint): string;
|
|
57
61
|
|
|
58
62
|
export { type Asset as A, type Chain as C, KNOWN_ASSETS as K, NATIVE_SOL as N, USDC_SOLANA_DEVNET as U, assetByKey as a, assetKey as b, resolveKnownAsset as c, formatAssetAmount as f, parseAssetAmount as p, resolveAssetFromPaymentRequest as r };
|
|
@@ -52,7 +52,11 @@ declare function resolveAssetFromPaymentRequest(request: {
|
|
|
52
52
|
* call-sites safe).
|
|
53
53
|
*/
|
|
54
54
|
declare function parseAssetAmount(asset: Asset, human: string): bigint;
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Format raw subunits back to `"<value> <SYMBOL>"`. Trailing zeros and a bare
|
|
57
|
+
* trailing dot are stripped, so 0.01 USDC renders as `"0.01 USDC"` rather than
|
|
58
|
+
* `"0.010000 USDC"`.
|
|
59
|
+
*/
|
|
56
60
|
declare function formatAssetAmount(asset: Asset, raw: bigint): string;
|
|
57
61
|
|
|
58
62
|
export { type Asset as A, type Chain as C, KNOWN_ASSETS as K, NATIVE_SOL as N, USDC_SOLANA_DEVNET as U, assetByKey as a, assetKey as b, resolveKnownAsset as c, formatAssetAmount as f, parseAssetAmount as p, resolveAssetFromPaymentRequest as r };
|
package/dist/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var system = require('@solana-program/system');
|
|
4
4
|
var token = require('@solana-program/token');
|
|
5
5
|
var kit = require('@solana/kit');
|
|
6
|
-
var
|
|
6
|
+
var Decimal3 = require('decimal.js-light');
|
|
7
7
|
var zod = require('zod');
|
|
8
8
|
var nostrTools = require('nostr-tools');
|
|
9
9
|
var nip44 = require('nostr-tools/nip44');
|
|
@@ -28,7 +28,7 @@ function _interopNamespace(e) {
|
|
|
28
28
|
return Object.freeze(n);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
var
|
|
31
|
+
var Decimal3__default = /*#__PURE__*/_interopDefault(Decimal3);
|
|
32
32
|
var nip44__namespace = /*#__PURE__*/_interopNamespace(nip44);
|
|
33
33
|
|
|
34
34
|
// src/constants.ts
|
|
@@ -176,8 +176,6 @@ async function getProtocolConfig(rpc, programId, options) {
|
|
|
176
176
|
);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
-
|
|
180
|
-
// src/payment/assets.ts
|
|
181
179
|
var NATIVE_SOL = {
|
|
182
180
|
chain: "solana",
|
|
183
181
|
token: "sol",
|
|
@@ -258,16 +256,10 @@ function parseAssetAmount(asset, human) {
|
|
|
258
256
|
}
|
|
259
257
|
return raw;
|
|
260
258
|
}
|
|
259
|
+
var FormatDecimal = Decimal3__default.default.clone({ toExpNeg: -100, toExpPos: 100, precision: 50 });
|
|
261
260
|
function formatAssetAmount(asset, raw) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
const unit = 10n ** BigInt(asset.decimals);
|
|
265
|
-
const whole = abs / unit;
|
|
266
|
-
const frac = abs % unit;
|
|
267
|
-
if (asset.decimals === 0) {
|
|
268
|
-
return `${sign}${whole} ${asset.symbol}`;
|
|
269
|
-
}
|
|
270
|
-
return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
|
|
261
|
+
const value = new FormatDecimal(raw.toString()).div(new FormatDecimal(10).pow(asset.decimals));
|
|
262
|
+
return `${value.toString()} ${asset.symbol}`;
|
|
271
263
|
}
|
|
272
264
|
var BPS_DENOMINATOR = 1e4;
|
|
273
265
|
function assertLamports(value, field) {
|
|
@@ -285,7 +277,7 @@ function calculateProtocolFee(amount, feeBps) {
|
|
|
285
277
|
if (amount === 0 || feeBps === 0) {
|
|
286
278
|
return 0;
|
|
287
279
|
}
|
|
288
|
-
return new
|
|
280
|
+
return new Decimal3__default.default(amount).mul(feeBps).div(BPS_DENOMINATOR).toDecimalPlaces(0, Decimal3__default.default.ROUND_CEIL).toNumber();
|
|
289
281
|
}
|
|
290
282
|
function validateExpiry(createdAt, expirySecs) {
|
|
291
283
|
if (!Number.isInteger(createdAt) || createdAt <= 0) {
|
|
@@ -1019,95 +1011,9 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
|
|
|
1019
1011
|
options
|
|
1020
1012
|
);
|
|
1021
1013
|
}
|
|
1022
|
-
var NEGATIVE_CACHE_TTL_MS = 6e4;
|
|
1023
|
-
var verifyCache = /* @__PURE__ */ new Map();
|
|
1024
|
-
function clearQuickVerifyCache() {
|
|
1025
|
-
verifyCache.clear();
|
|
1026
|
-
}
|
|
1027
|
-
async function verifyJobPaymentQuick(rpc, txSignature, expectedRecipient) {
|
|
1028
|
-
if (!txSignature) {
|
|
1029
|
-
return { verified: false, txSignature: "", reason: "invalid_input" };
|
|
1030
|
-
}
|
|
1031
|
-
if (!expectedRecipient || !kit.isAddress(expectedRecipient)) {
|
|
1032
|
-
return { verified: false, txSignature, reason: "invalid_input" };
|
|
1033
|
-
}
|
|
1034
|
-
const cacheKey2 = `${txSignature}:${expectedRecipient}`;
|
|
1035
|
-
const cached = verifyCache.get(cacheKey2);
|
|
1036
|
-
if (cached) {
|
|
1037
|
-
if (cached.result.verified) {
|
|
1038
|
-
return cached.result;
|
|
1039
|
-
}
|
|
1040
|
-
if (Date.now() - cached.cachedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
1041
|
-
return cached.result;
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
const result = await doVerifyOnce(rpc, txSignature, expectedRecipient);
|
|
1045
|
-
verifyCache.set(cacheKey2, { result, cachedAt: Date.now() });
|
|
1046
|
-
return result;
|
|
1047
|
-
}
|
|
1048
|
-
async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
|
|
1049
|
-
const sigStr = txSignature;
|
|
1050
|
-
if (!rpc || typeof rpc.getTransaction !== "function") {
|
|
1051
|
-
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
1052
|
-
}
|
|
1053
|
-
let tx;
|
|
1054
|
-
try {
|
|
1055
|
-
tx = await rpc.getTransaction(txSignature, {
|
|
1056
|
-
commitment: "confirmed",
|
|
1057
|
-
encoding: "json",
|
|
1058
|
-
maxSupportedTransactionVersion: 0
|
|
1059
|
-
}).send();
|
|
1060
|
-
} catch {
|
|
1061
|
-
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
1062
|
-
}
|
|
1063
|
-
if (!tx) {
|
|
1064
|
-
return { verified: false, txSignature: sigStr, reason: "not_found" };
|
|
1065
|
-
}
|
|
1066
|
-
if (!tx.meta || tx.meta.err) {
|
|
1067
|
-
return { verified: false, txSignature: sigStr, reason: "tx_failed" };
|
|
1068
|
-
}
|
|
1069
|
-
const accountKeys = tx.transaction.message.accountKeys;
|
|
1070
|
-
const recipientStr = expectedRecipient;
|
|
1071
|
-
const recipientIdx = accountKeys.indexOf(recipientStr);
|
|
1072
|
-
if (recipientIdx !== -1) {
|
|
1073
|
-
const preBalances = tx.meta.preBalances;
|
|
1074
|
-
const postBalances = tx.meta.postBalances;
|
|
1075
|
-
if (preBalances && postBalances) {
|
|
1076
|
-
const pre = preBalances[recipientIdx];
|
|
1077
|
-
const post = postBalances[recipientIdx];
|
|
1078
|
-
if (pre !== void 0 && post !== void 0) {
|
|
1079
|
-
const delta = BigInt(post) - BigInt(pre);
|
|
1080
|
-
if (delta > 0n) {
|
|
1081
|
-
return { verified: true, txSignature: sigStr };
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
const postTokenBalances = tx.meta.postTokenBalances;
|
|
1087
|
-
const preTokenBalances = tx.meta.preTokenBalances;
|
|
1088
|
-
if (postTokenBalances) {
|
|
1089
|
-
for (const post of postTokenBalances) {
|
|
1090
|
-
if (post.owner !== recipientStr) {
|
|
1091
|
-
continue;
|
|
1092
|
-
}
|
|
1093
|
-
const pre = preTokenBalances?.find(
|
|
1094
|
-
(entry) => entry.owner === recipientStr && entry.mint === post.mint
|
|
1095
|
-
);
|
|
1096
|
-
const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
|
|
1097
|
-
const postAmount = BigInt(post.uiTokenAmount.amount);
|
|
1098
|
-
if (postAmount > preAmount) {
|
|
1099
|
-
return { verified: true, txSignature: sigStr };
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
// src/services/discovery.ts
|
|
1107
1014
|
var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
|
|
1108
1015
|
var RANKING_BUCKET_SIZE_SECS = 60;
|
|
1109
1016
|
var COLD_START_BUCKET = -Infinity;
|
|
1110
|
-
var MAX_PAID_CANDIDATES_PER_AGENT = 5;
|
|
1111
1017
|
function toDTag(name) {
|
|
1112
1018
|
const tag = name.toLowerCase().replace(/[^a-z0-9\s-]/g, (ch) => "_" + ch.charCodeAt(0).toString(16).padStart(2, "0")).replace(/\s+/g, "-").replace(/^-+|-+$/g, "");
|
|
1113
1019
|
if (!tag) {
|
|
@@ -1231,37 +1137,9 @@ function buildAgentsFromEvents(events, network) {
|
|
|
1231
1137
|
}
|
|
1232
1138
|
return agentMap;
|
|
1233
1139
|
}
|
|
1234
|
-
function pickSolanaAddress(agent) {
|
|
1235
|
-
for (const card of agent.cards) {
|
|
1236
|
-
const addr = card.payment?.address;
|
|
1237
|
-
if (card.payment?.chain === "solana" && typeof addr === "string" && addr.length > 0) {
|
|
1238
|
-
return addr;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
return null;
|
|
1242
|
-
}
|
|
1243
|
-
async function verifyNewestPaidCandidate(rpc, recipient, candidatesNewestFirst) {
|
|
1244
|
-
const settled = await Promise.allSettled(
|
|
1245
|
-
candidatesNewestFirst.map(
|
|
1246
|
-
(candidate) => verifyJobPaymentQuick(rpc, candidate.txSignature, recipient)
|
|
1247
|
-
)
|
|
1248
|
-
);
|
|
1249
|
-
for (let i = 0; i < settled.length; i++) {
|
|
1250
|
-
const entry = settled[i];
|
|
1251
|
-
if (entry?.status === "fulfilled" && entry.value.verified) {
|
|
1252
|
-
return candidatesNewestFirst[i] ?? null;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
return null;
|
|
1256
|
-
}
|
|
1257
1140
|
var DiscoveryService = class {
|
|
1258
|
-
constructor(pool
|
|
1141
|
+
constructor(pool) {
|
|
1259
1142
|
this.pool = pool;
|
|
1260
|
-
this.defaultRpc = defaultRpc;
|
|
1261
|
-
}
|
|
1262
|
-
/** Configure the Solana RPC used for on-chain payment verification in `fetchAgents`. */
|
|
1263
|
-
setRpc(rpc) {
|
|
1264
|
-
this.defaultRpc = rpc;
|
|
1265
1143
|
}
|
|
1266
1144
|
/** Count elisym agents (kind:31990 with "elisym" tag). */
|
|
1267
1145
|
async fetchAllAgentCount() {
|
|
@@ -1350,20 +1228,26 @@ var DiscoveryService = class {
|
|
|
1350
1228
|
return agents;
|
|
1351
1229
|
}
|
|
1352
1230
|
/**
|
|
1353
|
-
* Fetch elisym agents filtered by network, ranked by
|
|
1231
|
+
* Fetch elisym agents filtered by network, ranked by paid-job recency and
|
|
1232
|
+
* positive-feedback rate.
|
|
1354
1233
|
*
|
|
1355
1234
|
* Ranking algorithm:
|
|
1356
|
-
* 1. Bucket each agent into 1-minute slots by `lastPaidJobAt` (
|
|
1357
|
-
*
|
|
1235
|
+
* 1. Bucket each agent into 1-minute slots by `lastPaidJobAt` (newest
|
|
1236
|
+
* `payment-completed` feedback timestamp). Cold-start agents go into a
|
|
1358
1237
|
* sentinel bucket below all populated buckets.
|
|
1359
1238
|
* 2. Within a bucket, sort by positive review rate descending.
|
|
1360
1239
|
* 3. Tiebreak by raw `lastPaidJobAt`, then `lastSeen` (NIP-89 freshness).
|
|
1361
1240
|
*
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1241
|
+
* NOTE: We trust the `payment-completed` feedback timestamp directly; we do
|
|
1242
|
+
* not verify the embedded `tx` signature on-chain. Public Solana devnet RPC
|
|
1243
|
+
* rate-limits trivially exceed what discovery needs (N agents * up-to-5
|
|
1244
|
+
* candidates), and the resulting 429s blocked discovery entirely. This
|
|
1245
|
+
* means a malicious customer can publish a fake `payment-completed` to lift
|
|
1246
|
+
* an agent's ranking. Acceptable trade-off for devnet / MVP; tighten via
|
|
1247
|
+
* recipient-tied checks when the network moves to mainnet with a paid RPC
|
|
1248
|
+
* provider.
|
|
1365
1249
|
*/
|
|
1366
|
-
async fetchAgents(network = "devnet", limit
|
|
1250
|
+
async fetchAgents(network = "devnet", limit) {
|
|
1367
1251
|
const filter = {
|
|
1368
1252
|
kinds: [KIND_APP_HANDLER],
|
|
1369
1253
|
"#t": ["elisym"]
|
|
@@ -1412,7 +1296,6 @@ var DiscoveryService = class {
|
|
|
1412
1296
|
agent.lastSeen = ev.created_at;
|
|
1413
1297
|
}
|
|
1414
1298
|
}
|
|
1415
|
-
const paidCandidates = /* @__PURE__ */ new Map();
|
|
1416
1299
|
for (const ev of feedbackEvents) {
|
|
1417
1300
|
if (!nostrTools.verifyEvent(ev)) {
|
|
1418
1301
|
continue;
|
|
@@ -1439,33 +1322,12 @@ var DiscoveryService = class {
|
|
|
1439
1322
|
const txTag = ev.tags.find((t) => t[0] === "tx");
|
|
1440
1323
|
const txSignature = txTag?.[1];
|
|
1441
1324
|
if (status === "payment-completed" && typeof txSignature === "string" && txSignature) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
list.push(candidate);
|
|
1446
|
-
} else {
|
|
1447
|
-
paidCandidates.set(targetPubkey, [candidate]);
|
|
1325
|
+
if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
|
|
1326
|
+
agent.lastPaidJobAt = ev.created_at;
|
|
1327
|
+
agent.lastPaidJobTx = txSignature;
|
|
1448
1328
|
}
|
|
1449
1329
|
}
|
|
1450
1330
|
}
|
|
1451
|
-
const rpc = rpcOverride ?? this.defaultRpc;
|
|
1452
|
-
if (rpc && paidCandidates.size > 0) {
|
|
1453
|
-
const perAgent = Array.from(paidCandidates.entries()).map(([agentPubkey, list]) => {
|
|
1454
|
-
const agent = agentMap.get(agentPubkey);
|
|
1455
|
-
const recipient = agent ? pickSolanaAddress(agent) : null;
|
|
1456
|
-
if (!agent || !recipient) {
|
|
1457
|
-
return Promise.resolve();
|
|
1458
|
-
}
|
|
1459
|
-
const ordered = [...list].sort((a, b) => b.createdAt - a.createdAt).slice(0, MAX_PAID_CANDIDATES_PER_AGENT);
|
|
1460
|
-
return verifyNewestPaidCandidate(rpc, recipient, ordered).then((winner) => {
|
|
1461
|
-
if (winner) {
|
|
1462
|
-
agent.lastPaidJobAt = winner.createdAt;
|
|
1463
|
-
agent.lastPaidJobTx = winner.txSignature;
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
|
-
});
|
|
1467
|
-
await Promise.all(perAgent);
|
|
1468
|
-
}
|
|
1469
1331
|
agents.sort(compareAgentsByRank);
|
|
1470
1332
|
return agents;
|
|
1471
1333
|
}
|
|
@@ -2165,6 +2027,7 @@ var MarketplaceService = class {
|
|
|
2165
2027
|
let status = "processing";
|
|
2166
2028
|
let amount;
|
|
2167
2029
|
let txHash;
|
|
2030
|
+
let asset;
|
|
2168
2031
|
if (result) {
|
|
2169
2032
|
status = "success";
|
|
2170
2033
|
const amtTag = result.tags.find((t) => t[0] === "amount");
|
|
@@ -2173,9 +2036,18 @@ var MarketplaceService = class {
|
|
|
2173
2036
|
const allFeedbacksForReq = feedbacksByRequestId.get(req.id) ?? [];
|
|
2174
2037
|
for (const fb of allFeedbacksForReq) {
|
|
2175
2038
|
const txTag = fb.tags.find((t) => t[0] === "tx");
|
|
2176
|
-
if (txTag?.[1]) {
|
|
2039
|
+
if (txTag?.[1] && !txHash) {
|
|
2177
2040
|
txHash = txTag[1];
|
|
2178
|
-
|
|
2041
|
+
}
|
|
2042
|
+
if (!asset) {
|
|
2043
|
+
const amtTag = fb.tags.find((t) => t[0] === "amount");
|
|
2044
|
+
const requestJson = amtTag?.[2];
|
|
2045
|
+
if (requestJson) {
|
|
2046
|
+
const parsed = parsePaymentRequest(requestJson);
|
|
2047
|
+
if (parsed.ok && parsed.data.asset) {
|
|
2048
|
+
asset = parsed.data.asset;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2179
2051
|
}
|
|
2180
2052
|
}
|
|
2181
2053
|
if (feedback) {
|
|
@@ -2204,6 +2076,7 @@ var MarketplaceService = class {
|
|
|
2204
2076
|
resultEventId: result?.id,
|
|
2205
2077
|
amount,
|
|
2206
2078
|
txHash,
|
|
2079
|
+
asset,
|
|
2207
2080
|
createdAt: req.created_at
|
|
2208
2081
|
});
|
|
2209
2082
|
}
|
|
@@ -2814,7 +2687,7 @@ var ElisymClient = class {
|
|
|
2814
2687
|
payment;
|
|
2815
2688
|
constructor(config = {}) {
|
|
2816
2689
|
this.pool = new NostrPool(config.relays ?? RELAYS);
|
|
2817
|
-
this.discovery = new DiscoveryService(this.pool
|
|
2690
|
+
this.discovery = new DiscoveryService(this.pool);
|
|
2818
2691
|
this.marketplace = new MarketplaceService(this.pool);
|
|
2819
2692
|
this.ping = new PingService(this.pool);
|
|
2820
2693
|
this.media = new MediaService(config.uploadUrl);
|
|
@@ -2942,6 +2815,89 @@ function lamportsToSol(lamports) {
|
|
|
2942
2815
|
const frac = lamports % LAMPORTS_PER_SOL2;
|
|
2943
2816
|
return `${whole}.${frac.toString().padStart(9, "0")}`;
|
|
2944
2817
|
}
|
|
2818
|
+
var NEGATIVE_CACHE_TTL_MS = 6e4;
|
|
2819
|
+
var verifyCache = /* @__PURE__ */ new Map();
|
|
2820
|
+
function clearQuickVerifyCache() {
|
|
2821
|
+
verifyCache.clear();
|
|
2822
|
+
}
|
|
2823
|
+
async function verifyJobPaymentQuick(rpc, txSignature, expectedRecipient) {
|
|
2824
|
+
if (!txSignature) {
|
|
2825
|
+
return { verified: false, txSignature: "", reason: "invalid_input" };
|
|
2826
|
+
}
|
|
2827
|
+
if (!expectedRecipient || !kit.isAddress(expectedRecipient)) {
|
|
2828
|
+
return { verified: false, txSignature, reason: "invalid_input" };
|
|
2829
|
+
}
|
|
2830
|
+
const cacheKey2 = `${txSignature}:${expectedRecipient}`;
|
|
2831
|
+
const cached = verifyCache.get(cacheKey2);
|
|
2832
|
+
if (cached) {
|
|
2833
|
+
if (cached.result.verified) {
|
|
2834
|
+
return cached.result;
|
|
2835
|
+
}
|
|
2836
|
+
if (Date.now() - cached.cachedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
2837
|
+
return cached.result;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
const result = await doVerifyOnce(rpc, txSignature, expectedRecipient);
|
|
2841
|
+
verifyCache.set(cacheKey2, { result, cachedAt: Date.now() });
|
|
2842
|
+
return result;
|
|
2843
|
+
}
|
|
2844
|
+
async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
|
|
2845
|
+
const sigStr = txSignature;
|
|
2846
|
+
if (!rpc || typeof rpc.getTransaction !== "function") {
|
|
2847
|
+
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
2848
|
+
}
|
|
2849
|
+
let tx;
|
|
2850
|
+
try {
|
|
2851
|
+
tx = await rpc.getTransaction(txSignature, {
|
|
2852
|
+
commitment: "confirmed",
|
|
2853
|
+
encoding: "json",
|
|
2854
|
+
maxSupportedTransactionVersion: 0
|
|
2855
|
+
}).send();
|
|
2856
|
+
} catch {
|
|
2857
|
+
return { verified: false, txSignature: sigStr, reason: "rpc_error" };
|
|
2858
|
+
}
|
|
2859
|
+
if (!tx) {
|
|
2860
|
+
return { verified: false, txSignature: sigStr, reason: "not_found" };
|
|
2861
|
+
}
|
|
2862
|
+
if (!tx.meta || tx.meta.err) {
|
|
2863
|
+
return { verified: false, txSignature: sigStr, reason: "tx_failed" };
|
|
2864
|
+
}
|
|
2865
|
+
const accountKeys = tx.transaction.message.accountKeys;
|
|
2866
|
+
const recipientStr = expectedRecipient;
|
|
2867
|
+
const recipientIdx = accountKeys.indexOf(recipientStr);
|
|
2868
|
+
if (recipientIdx !== -1) {
|
|
2869
|
+
const preBalances = tx.meta.preBalances;
|
|
2870
|
+
const postBalances = tx.meta.postBalances;
|
|
2871
|
+
if (preBalances && postBalances) {
|
|
2872
|
+
const pre = preBalances[recipientIdx];
|
|
2873
|
+
const post = postBalances[recipientIdx];
|
|
2874
|
+
if (pre !== void 0 && post !== void 0) {
|
|
2875
|
+
const delta = BigInt(post) - BigInt(pre);
|
|
2876
|
+
if (delta > 0n) {
|
|
2877
|
+
return { verified: true, txSignature: sigStr };
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
const postTokenBalances = tx.meta.postTokenBalances;
|
|
2883
|
+
const preTokenBalances = tx.meta.preTokenBalances;
|
|
2884
|
+
if (postTokenBalances) {
|
|
2885
|
+
for (const post of postTokenBalances) {
|
|
2886
|
+
if (post.owner !== recipientStr) {
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
const pre = preTokenBalances?.find(
|
|
2890
|
+
(entry) => entry.owner === recipientStr && entry.mint === post.mint
|
|
2891
|
+
);
|
|
2892
|
+
const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
|
|
2893
|
+
const postAmount = BigInt(post.uiTokenAmount.amount);
|
|
2894
|
+
if (postAmount > preAmount) {
|
|
2895
|
+
return { verified: true, txSignature: sigStr };
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
|
|
2900
|
+
}
|
|
2945
2901
|
var SessionSpendLimitEntrySchema = zod.z.object({
|
|
2946
2902
|
chain: zod.z.enum(["solana"]),
|
|
2947
2903
|
token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
|
|
@@ -2952,7 +2908,7 @@ var GlobalConfigSchema = zod.z.object({
|
|
|
2952
2908
|
session_spend_limits: zod.z.array(SessionSpendLimitEntrySchema).max(16).optional()
|
|
2953
2909
|
}).strict();
|
|
2954
2910
|
function formatSol(lamports) {
|
|
2955
|
-
const sol = new
|
|
2911
|
+
const sol = new Decimal3__default.default(lamports).div(LAMPORTS_PER_SOL);
|
|
2956
2912
|
if (sol.gte(1e6)) {
|
|
2957
2913
|
return `${sol.idiv(1e6)}m SOL`;
|
|
2958
2914
|
}
|
|
@@ -2966,12 +2922,12 @@ function compactSol(sol) {
|
|
|
2966
2922
|
return "0";
|
|
2967
2923
|
}
|
|
2968
2924
|
if (sol.gte(1e3)) {
|
|
2969
|
-
return sol.toDecimalPlaces(0,
|
|
2925
|
+
return sol.toDecimalPlaces(0, Decimal3__default.default.ROUND_FLOOR).toString();
|
|
2970
2926
|
}
|
|
2971
2927
|
const maxFrac = 9;
|
|
2972
2928
|
for (let d = 1; d <= maxFrac; d++) {
|
|
2973
2929
|
const s = sol.toFixed(d);
|
|
2974
|
-
if (new
|
|
2930
|
+
if (new Decimal3__default.default(s).eq(sol)) {
|
|
2975
2931
|
return s.replace(/0+$/, "").replace(/\.$/, "");
|
|
2976
2932
|
}
|
|
2977
2933
|
}
|