@elisym/sdk 0.8.0 → 0.10.0
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 +31 -10
- package/dist/agent-store.cjs +1 -2
- package/dist/agent-store.cjs.map +1 -1
- package/dist/agent-store.d.cts +19 -16
- package/dist/agent-store.d.ts +19 -16
- package/dist/agent-store.js +1 -2
- package/dist/agent-store.js.map +1 -1
- package/dist/assets-CMf-v55Z.d.cts +58 -0
- package/dist/assets-CMf-v55Z.d.ts +58 -0
- package/dist/global-schema-CddHP2nk.d.cts +62 -0
- package/dist/global-schema-CddHP2nk.d.ts +62 -0
- package/dist/index.cjs +642 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -117
- package/dist/index.d.ts +214 -117
- package/dist/index.js +636 -168
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +60 -0
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +21 -1
- package/dist/node.d.ts +21 -1
- package/dist/node.js +55 -1
- package/dist/node.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +14 -0
- package/dist/runtime.d.ts +14 -0
- package/dist/runtime.js.map +1 -1
- package/dist/skills.cjs +123 -10
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +17 -4
- package/dist/skills.d.ts +17 -4
- package/dist/skills.js +123 -10
- package/dist/skills.js.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var system = require('@solana-program/system');
|
|
4
|
+
var token = require('@solana-program/token');
|
|
4
5
|
var kit = require('@solana/kit');
|
|
5
6
|
var Decimal2 = require('decimal.js-light');
|
|
6
7
|
var zod = require('zod');
|
|
7
8
|
var nostrTools = require('nostr-tools');
|
|
8
9
|
var nip44 = require('nostr-tools/nip44');
|
|
9
|
-
var promises = require('fs/promises');
|
|
10
|
-
var YAML2 = require('yaml');
|
|
11
|
-
var crypto$1 = require('crypto');
|
|
12
|
-
var path = require('path');
|
|
13
10
|
|
|
14
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
12
|
|
|
@@ -33,7 +30,6 @@ function _interopNamespace(e) {
|
|
|
33
30
|
|
|
34
31
|
var Decimal2__default = /*#__PURE__*/_interopDefault(Decimal2);
|
|
35
32
|
var nip44__namespace = /*#__PURE__*/_interopNamespace(nip44);
|
|
36
|
-
var YAML2__default = /*#__PURE__*/_interopDefault(YAML2);
|
|
37
33
|
|
|
38
34
|
// src/constants.ts
|
|
39
35
|
var RELAYS = [
|
|
@@ -79,7 +75,7 @@ function getProtocolProgramId(cluster) {
|
|
|
79
75
|
}
|
|
80
76
|
var DEFAULTS = {
|
|
81
77
|
SUBSCRIPTION_TIMEOUT_MS: 12e4,
|
|
82
|
-
PING_TIMEOUT_MS:
|
|
78
|
+
PING_TIMEOUT_MS: 3e3,
|
|
83
79
|
PING_RETRIES: 2,
|
|
84
80
|
PING_CACHE_TTL_MS: 3e4,
|
|
85
81
|
PAYMENT_EXPIRY_SECS: 600,
|
|
@@ -123,13 +119,13 @@ function decodeConfig(encodedAccount) {
|
|
|
123
119
|
getConfigDecoder()
|
|
124
120
|
);
|
|
125
121
|
}
|
|
126
|
-
async function fetchConfig(rpc,
|
|
127
|
-
const maybeAccount = await fetchMaybeConfig(rpc,
|
|
122
|
+
async function fetchConfig(rpc, address3, config) {
|
|
123
|
+
const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
|
|
128
124
|
kit.assertAccountExists(maybeAccount);
|
|
129
125
|
return maybeAccount;
|
|
130
126
|
}
|
|
131
|
-
async function fetchMaybeConfig(rpc,
|
|
132
|
-
const maybeAccount = await kit.fetchEncodedAccount(rpc,
|
|
127
|
+
async function fetchMaybeConfig(rpc, address3, config) {
|
|
128
|
+
const maybeAccount = await kit.fetchEncodedAccount(rpc, address3, config);
|
|
133
129
|
return decodeConfig(maybeAccount);
|
|
134
130
|
}
|
|
135
131
|
if (process.env.NODE_ENV !== "production") ;
|
|
@@ -180,6 +176,99 @@ async function getProtocolConfig(rpc, programId, options) {
|
|
|
180
176
|
);
|
|
181
177
|
}
|
|
182
178
|
}
|
|
179
|
+
|
|
180
|
+
// src/payment/assets.ts
|
|
181
|
+
var NATIVE_SOL = {
|
|
182
|
+
chain: "solana",
|
|
183
|
+
token: "sol",
|
|
184
|
+
decimals: 9,
|
|
185
|
+
symbol: "SOL"
|
|
186
|
+
};
|
|
187
|
+
var USDC_SOLANA_DEVNET = {
|
|
188
|
+
chain: "solana",
|
|
189
|
+
token: "usdc",
|
|
190
|
+
mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
191
|
+
decimals: 6,
|
|
192
|
+
symbol: "USDC"
|
|
193
|
+
};
|
|
194
|
+
var KNOWN_ASSETS = [NATIVE_SOL, USDC_SOLANA_DEVNET];
|
|
195
|
+
function assetKey(a) {
|
|
196
|
+
return a.mint ? `${a.chain}:${a.token}:${a.mint}` : `${a.chain}:${a.token}`;
|
|
197
|
+
}
|
|
198
|
+
function resolveKnownAsset(chain, token, mint) {
|
|
199
|
+
const key = mint ? `${chain}:${token}:${mint}` : `${chain}:${token}`;
|
|
200
|
+
return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
|
|
201
|
+
}
|
|
202
|
+
function assetByKey(key) {
|
|
203
|
+
return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
|
|
204
|
+
}
|
|
205
|
+
function resolveAssetFromPaymentRequest(request) {
|
|
206
|
+
if (!request.asset) {
|
|
207
|
+
return NATIVE_SOL;
|
|
208
|
+
}
|
|
209
|
+
const found = resolveKnownAsset(request.asset.chain, request.asset.token, request.asset.mint);
|
|
210
|
+
if (!found) {
|
|
211
|
+
const display = request.asset.mint ? `${request.asset.chain}:${request.asset.token}:${request.asset.mint}` : `${request.asset.chain}:${request.asset.token}`;
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Unknown asset in payment request: ${display}. Known assets: ${KNOWN_ASSETS.map(assetKey).join(", ")}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return found;
|
|
217
|
+
}
|
|
218
|
+
var DECIMAL_RE = /^(\d+\.\d*|\d*\.\d+|\d+)$/;
|
|
219
|
+
function parseAssetAmount(asset, human) {
|
|
220
|
+
const trimmed = human.trim();
|
|
221
|
+
if (!trimmed) {
|
|
222
|
+
throw new Error(`${asset.symbol} amount is empty`);
|
|
223
|
+
}
|
|
224
|
+
if (trimmed.startsWith("-")) {
|
|
225
|
+
throw new Error(`${asset.symbol} amount cannot be negative`);
|
|
226
|
+
}
|
|
227
|
+
if (!DECIMAL_RE.test(trimmed)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`${asset.symbol} amount must be a non-negative decimal (e.g. "0.5", "1"); got "${human}"`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const dotPos = trimmed.indexOf(".");
|
|
233
|
+
let wholePart;
|
|
234
|
+
if (dotPos === -1) {
|
|
235
|
+
wholePart = trimmed;
|
|
236
|
+
} else if (dotPos === 0) {
|
|
237
|
+
wholePart = "0";
|
|
238
|
+
} else {
|
|
239
|
+
wholePart = trimmed.slice(0, dotPos);
|
|
240
|
+
}
|
|
241
|
+
const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
|
|
242
|
+
if (fracPart.length > asset.decimals) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const unit = 10n ** BigInt(asset.decimals);
|
|
248
|
+
const whole = BigInt(wholePart);
|
|
249
|
+
const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
|
|
250
|
+
const raw = whole * unit + frac;
|
|
251
|
+
if (raw === 0n) {
|
|
252
|
+
throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
|
|
253
|
+
}
|
|
254
|
+
if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return raw;
|
|
260
|
+
}
|
|
261
|
+
function formatAssetAmount(asset, raw) {
|
|
262
|
+
const sign = raw < 0n ? "-" : "";
|
|
263
|
+
const abs = raw < 0n ? -raw : raw;
|
|
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}`;
|
|
271
|
+
}
|
|
183
272
|
var BPS_DENOMINATOR = 1e4;
|
|
184
273
|
function assertLamports(value, field) {
|
|
185
274
|
if (!Number.isInteger(value) || value < 0) {
|
|
@@ -289,6 +378,12 @@ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
|
|
|
289
378
|
var lamportsSchema = zod.z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
|
|
290
379
|
var feeAmountSchema = zod.z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
|
|
291
380
|
var solanaAddressSchema = zod.z.string().regex(BASE58_RE, "must be base58").regex(SOLANA_ADDRESS_LENGTH_RE, "must be 32-44 base58 chars");
|
|
381
|
+
var paymentAssetRefSchema = zod.z.object({
|
|
382
|
+
chain: zod.z.string().min(1),
|
|
383
|
+
token: zod.z.string().min(1),
|
|
384
|
+
mint: solanaAddressSchema.optional(),
|
|
385
|
+
decimals: zod.z.number().int().min(0).max(18)
|
|
386
|
+
});
|
|
292
387
|
var PaymentRequestSchema = zod.z.object({
|
|
293
388
|
recipient: solanaAddressSchema,
|
|
294
389
|
amount: lamportsSchema,
|
|
@@ -297,7 +392,8 @@ var PaymentRequestSchema = zod.z.object({
|
|
|
297
392
|
fee_address: solanaAddressSchema.optional(),
|
|
298
393
|
fee_amount: feeAmountSchema.optional(),
|
|
299
394
|
created_at: zod.z.number().int().positive(),
|
|
300
|
-
expiry_secs: zod.z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`)
|
|
395
|
+
expiry_secs: zod.z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`),
|
|
396
|
+
asset: paymentAssetRefSchema.optional()
|
|
301
397
|
});
|
|
302
398
|
function parsePaymentRequest(input, options) {
|
|
303
399
|
let parsed;
|
|
@@ -379,6 +475,12 @@ var SolanaPaymentStrategy = class {
|
|
|
379
475
|
assertExpirySecs(expirySecs);
|
|
380
476
|
const feeAmount = calculateProtocolFee(amount, config.feeBps);
|
|
381
477
|
const reference = generateReference();
|
|
478
|
+
const assetRef = options?.asset && options.asset !== NATIVE_SOL ? {
|
|
479
|
+
chain: options.asset.chain,
|
|
480
|
+
token: options.asset.token,
|
|
481
|
+
mint: options.asset.mint,
|
|
482
|
+
decimals: options.asset.decimals
|
|
483
|
+
} : void 0;
|
|
382
484
|
return {
|
|
383
485
|
recipient: recipientAddress,
|
|
384
486
|
amount,
|
|
@@ -386,7 +488,8 @@ var SolanaPaymentStrategy = class {
|
|
|
386
488
|
fee_address: config.treasury,
|
|
387
489
|
fee_amount: feeAmount,
|
|
388
490
|
created_at: Math.floor(Date.now() / 1e3),
|
|
389
|
-
expiry_secs: expirySecs
|
|
491
|
+
expiry_secs: expirySecs,
|
|
492
|
+
...assetRef ? { asset: assetRef } : {}
|
|
390
493
|
};
|
|
391
494
|
}
|
|
392
495
|
validatePaymentRequest(requestJson, config, expectedRecipient, options) {
|
|
@@ -404,6 +507,16 @@ var SolanaPaymentStrategy = class {
|
|
|
404
507
|
return { code: "invalid_amount", message: parsed.error.message };
|
|
405
508
|
}
|
|
406
509
|
const data = parsed.data;
|
|
510
|
+
if (data.asset) {
|
|
511
|
+
try {
|
|
512
|
+
resolveAssetFromPaymentRequest(data);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
return {
|
|
515
|
+
code: "invalid_asset",
|
|
516
|
+
message: error instanceof Error ? error.message : String(error)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}
|
|
407
520
|
if (!isValidSolanaAddress(data.recipient)) {
|
|
408
521
|
return {
|
|
409
522
|
code: "invalid_recipient_address",
|
|
@@ -492,7 +605,7 @@ var SolanaPaymentStrategy = class {
|
|
|
492
605
|
if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
|
|
493
606
|
throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
|
|
494
607
|
}
|
|
495
|
-
const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
|
|
608
|
+
const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner);
|
|
496
609
|
const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
|
|
497
610
|
percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
|
|
498
611
|
});
|
|
@@ -554,6 +667,16 @@ var SolanaPaymentStrategy = class {
|
|
|
554
667
|
error: `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount})`
|
|
555
668
|
};
|
|
556
669
|
}
|
|
670
|
+
let asset;
|
|
671
|
+
try {
|
|
672
|
+
asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
return {
|
|
675
|
+
verified: false,
|
|
676
|
+
error: error instanceof Error ? error.message : String(error)
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const mint = asset.mint;
|
|
557
680
|
if (options?.txSignature) {
|
|
558
681
|
return this._verifyBySignature(
|
|
559
682
|
rpc,
|
|
@@ -563,6 +686,7 @@ var SolanaPaymentStrategy = class {
|
|
|
563
686
|
treasury,
|
|
564
687
|
expectedNet,
|
|
565
688
|
feeAmount,
|
|
689
|
+
mint,
|
|
566
690
|
options?.retries ?? DEFAULTS.VERIFY_RETRIES,
|
|
567
691
|
options?.intervalMs ?? DEFAULTS.VERIFY_INTERVAL_MS
|
|
568
692
|
);
|
|
@@ -574,11 +698,12 @@ var SolanaPaymentStrategy = class {
|
|
|
574
698
|
treasury,
|
|
575
699
|
expectedNet,
|
|
576
700
|
feeAmount,
|
|
701
|
+
mint,
|
|
577
702
|
options?.retries ?? DEFAULTS.VERIFY_BY_REF_RETRIES,
|
|
578
703
|
options?.intervalMs ?? DEFAULTS.VERIFY_BY_REF_INTERVAL_MS
|
|
579
704
|
);
|
|
580
705
|
}
|
|
581
|
-
async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
|
|
706
|
+
async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
|
|
582
707
|
let lastError;
|
|
583
708
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
584
709
|
try {
|
|
@@ -597,15 +722,18 @@ var SolanaPaymentStrategy = class {
|
|
|
597
722
|
error: tx?.meta?.err ? "Transaction failed on-chain" : "Transaction not found"
|
|
598
723
|
};
|
|
599
724
|
}
|
|
600
|
-
const verdict =
|
|
725
|
+
const verdict = checkTxDiff({
|
|
601
726
|
accountKeys: tx.transaction.message.accountKeys,
|
|
602
727
|
preBalances: tx.meta.preBalances,
|
|
603
728
|
postBalances: tx.meta.postBalances,
|
|
729
|
+
preTokenBalances: tx.meta.preTokenBalances,
|
|
730
|
+
postTokenBalances: tx.meta.postTokenBalances,
|
|
604
731
|
referenceKey,
|
|
605
732
|
recipientAddress,
|
|
606
733
|
treasuryAddress,
|
|
607
734
|
expectedNet,
|
|
608
|
-
expectedFee
|
|
735
|
+
expectedFee,
|
|
736
|
+
mint
|
|
609
737
|
});
|
|
610
738
|
if (verdict.ok) {
|
|
611
739
|
return { verified: true, txSignature };
|
|
@@ -623,7 +751,7 @@ var SolanaPaymentStrategy = class {
|
|
|
623
751
|
error: `Verification failed after ${retries} retries: ${lastError instanceof Error ? lastError.message : "unknown error"}`
|
|
624
752
|
};
|
|
625
753
|
}
|
|
626
|
-
async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
|
|
754
|
+
async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
|
|
627
755
|
let lastError;
|
|
628
756
|
const reference = kit.address(referenceKey);
|
|
629
757
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
@@ -647,15 +775,18 @@ var SolanaPaymentStrategy = class {
|
|
|
647
775
|
if (!tx?.meta || tx.meta.err) {
|
|
648
776
|
continue;
|
|
649
777
|
}
|
|
650
|
-
const verdict =
|
|
778
|
+
const verdict = checkTxDiff({
|
|
651
779
|
accountKeys: tx.transaction.message.accountKeys,
|
|
652
780
|
preBalances: tx.meta.preBalances,
|
|
653
781
|
postBalances: tx.meta.postBalances,
|
|
782
|
+
preTokenBalances: tx.meta.preTokenBalances,
|
|
783
|
+
postTokenBalances: tx.meta.postTokenBalances,
|
|
654
784
|
referenceKey,
|
|
655
785
|
recipientAddress,
|
|
656
786
|
treasuryAddress,
|
|
657
787
|
expectedNet,
|
|
658
|
-
expectedFee
|
|
788
|
+
expectedFee,
|
|
789
|
+
mint
|
|
659
790
|
});
|
|
660
791
|
if (verdict.ok) {
|
|
661
792
|
return { verified: true, txSignature: sig };
|
|
@@ -675,7 +806,7 @@ var SolanaPaymentStrategy = class {
|
|
|
675
806
|
};
|
|
676
807
|
}
|
|
677
808
|
};
|
|
678
|
-
function
|
|
809
|
+
function checkTxDiff(input) {
|
|
679
810
|
const balanceCount = input.preBalances.length;
|
|
680
811
|
const keyToIdx = /* @__PURE__ */ new Map();
|
|
681
812
|
for (let i = 0; i < Math.min(input.accountKeys.length, balanceCount); i++) {
|
|
@@ -687,6 +818,9 @@ function checkBalanceDiff(input) {
|
|
|
687
818
|
if (!keyToIdx.has(input.referenceKey)) {
|
|
688
819
|
return { ok: false, reason: "Reference key not found in transaction - possible replay" };
|
|
689
820
|
}
|
|
821
|
+
if (input.mint) {
|
|
822
|
+
return checkTokenBalanceDiff(input);
|
|
823
|
+
}
|
|
690
824
|
const recipientIdx = keyToIdx.get(input.recipientAddress);
|
|
691
825
|
if (recipientIdx === void 0) {
|
|
692
826
|
return { ok: false, reason: "Recipient not found in transaction" };
|
|
@@ -719,6 +853,47 @@ function checkBalanceDiff(input) {
|
|
|
719
853
|
}
|
|
720
854
|
return { ok: true };
|
|
721
855
|
}
|
|
856
|
+
function checkTokenBalanceDiff(input) {
|
|
857
|
+
const mint = input.mint;
|
|
858
|
+
if (!mint) {
|
|
859
|
+
return { ok: false, reason: "Expected mint for SPL verification, got none" };
|
|
860
|
+
}
|
|
861
|
+
const pre = input.preTokenBalances ?? [];
|
|
862
|
+
const post = input.postTokenBalances ?? [];
|
|
863
|
+
const tokenDelta = (ownerAddress) => {
|
|
864
|
+
const preEntry = pre.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
|
|
865
|
+
const postEntry = post.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
|
|
866
|
+
if (!postEntry) {
|
|
867
|
+
return -1n;
|
|
868
|
+
}
|
|
869
|
+
const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
|
|
870
|
+
const postAmount = BigInt(postEntry.uiTokenAmount.amount);
|
|
871
|
+
return postAmount - preAmount;
|
|
872
|
+
};
|
|
873
|
+
const recipientDelta = tokenDelta(input.recipientAddress);
|
|
874
|
+
if (recipientDelta === -1n) {
|
|
875
|
+
return { ok: false, reason: "Recipient token account not found in transaction" };
|
|
876
|
+
}
|
|
877
|
+
if (recipientDelta < BigInt(input.expectedNet)) {
|
|
878
|
+
return {
|
|
879
|
+
ok: false,
|
|
880
|
+
reason: `Recipient received ${recipientDelta.toString()} tokens, expected >= ${input.expectedNet}`
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
if (input.expectedFee > 0) {
|
|
884
|
+
const treasuryDelta = tokenDelta(input.treasuryAddress);
|
|
885
|
+
if (treasuryDelta === -1n) {
|
|
886
|
+
return { ok: false, reason: "Treasury token account not found in transaction" };
|
|
887
|
+
}
|
|
888
|
+
if (treasuryDelta < BigInt(input.expectedFee)) {
|
|
889
|
+
return {
|
|
890
|
+
ok: false,
|
|
891
|
+
reason: `Treasury received ${treasuryDelta.toString()} tokens, expected >= ${input.expectedFee}`
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return { ok: true };
|
|
896
|
+
}
|
|
722
897
|
function bigIntDelta(post, pre) {
|
|
723
898
|
const postValue = post === void 0 ? 0n : BigInt(post);
|
|
724
899
|
const preValue = pre === void 0 ? 0n : BigInt(pre);
|
|
@@ -727,7 +902,7 @@ function bigIntDelta(post, pre) {
|
|
|
727
902
|
function waitMs(ms) {
|
|
728
903
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
729
904
|
}
|
|
730
|
-
function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
905
|
+
async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
731
906
|
const recipient = kit.address(paymentRequest.recipient);
|
|
732
907
|
const reference = kit.address(paymentRequest.reference);
|
|
733
908
|
const feeAmount = paymentRequest.fee_amount ?? 0;
|
|
@@ -737,22 +912,98 @@ function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
|
737
912
|
`Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
|
|
738
913
|
);
|
|
739
914
|
}
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
915
|
+
const asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
916
|
+
if (!asset.mint) {
|
|
917
|
+
const providerTransferIx2 = system.getTransferSolInstruction({
|
|
918
|
+
source: payerSigner,
|
|
919
|
+
destination: recipient,
|
|
920
|
+
amount: BigInt(providerAmount)
|
|
921
|
+
});
|
|
922
|
+
const providerTransferIxWithReference2 = {
|
|
923
|
+
...providerTransferIx2,
|
|
924
|
+
accounts: [
|
|
925
|
+
...providerTransferIx2.accounts,
|
|
926
|
+
{ address: reference, role: kit.AccountRole.READONLY }
|
|
927
|
+
]
|
|
928
|
+
};
|
|
929
|
+
const instructions2 = [providerTransferIxWithReference2];
|
|
930
|
+
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
931
|
+
instructions2.push(
|
|
932
|
+
system.getTransferSolInstruction({
|
|
933
|
+
source: payerSigner,
|
|
934
|
+
destination: kit.address(paymentRequest.fee_address),
|
|
935
|
+
amount: BigInt(feeAmount)
|
|
936
|
+
})
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
return instructions2;
|
|
940
|
+
}
|
|
941
|
+
const mint = kit.address(asset.mint);
|
|
942
|
+
const payerAddress = payerSigner.address;
|
|
943
|
+
const [payerAta] = await token.findAssociatedTokenPda({
|
|
944
|
+
owner: payerAddress,
|
|
945
|
+
tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
|
|
946
|
+
mint
|
|
947
|
+
});
|
|
948
|
+
const [recipientAta] = await token.findAssociatedTokenPda({
|
|
949
|
+
owner: recipient,
|
|
950
|
+
tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
|
|
951
|
+
mint
|
|
952
|
+
});
|
|
953
|
+
const instructions = [];
|
|
954
|
+
instructions.push(
|
|
955
|
+
token.getCreateAssociatedTokenIdempotentInstruction(
|
|
956
|
+
{
|
|
957
|
+
payer: payerSigner,
|
|
958
|
+
ata: recipientAta,
|
|
959
|
+
owner: recipient,
|
|
960
|
+
mint
|
|
961
|
+
},
|
|
962
|
+
{ programAddress: token.ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
|
|
963
|
+
)
|
|
964
|
+
);
|
|
965
|
+
let treasuryAta;
|
|
966
|
+
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
967
|
+
const treasuryOwner = kit.address(paymentRequest.fee_address);
|
|
968
|
+
[treasuryAta] = await token.findAssociatedTokenPda({
|
|
969
|
+
owner: treasuryOwner,
|
|
970
|
+
tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
|
|
971
|
+
mint
|
|
972
|
+
});
|
|
973
|
+
instructions.push(
|
|
974
|
+
token.getCreateAssociatedTokenIdempotentInstruction(
|
|
975
|
+
{
|
|
976
|
+
payer: payerSigner,
|
|
977
|
+
ata: treasuryAta,
|
|
978
|
+
owner: treasuryOwner,
|
|
979
|
+
mint
|
|
980
|
+
},
|
|
981
|
+
{ programAddress: token.ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
|
|
982
|
+
)
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
const providerTransferIx = token.getTransferCheckedInstruction({
|
|
986
|
+
source: payerAta,
|
|
987
|
+
mint,
|
|
988
|
+
destination: recipientAta,
|
|
989
|
+
authority: payerSigner,
|
|
990
|
+
amount: BigInt(providerAmount),
|
|
991
|
+
decimals: asset.decimals
|
|
744
992
|
});
|
|
745
993
|
const providerTransferIxWithReference = {
|
|
746
994
|
...providerTransferIx,
|
|
747
995
|
accounts: [...providerTransferIx.accounts, { address: reference, role: kit.AccountRole.READONLY }]
|
|
748
996
|
};
|
|
749
|
-
|
|
750
|
-
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
997
|
+
instructions.push(providerTransferIxWithReference);
|
|
998
|
+
if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
|
|
751
999
|
instructions.push(
|
|
752
|
-
|
|
753
|
-
source:
|
|
754
|
-
|
|
755
|
-
|
|
1000
|
+
token.getTransferCheckedInstruction({
|
|
1001
|
+
source: payerAta,
|
|
1002
|
+
mint,
|
|
1003
|
+
destination: treasuryAta,
|
|
1004
|
+
authority: payerSigner,
|
|
1005
|
+
amount: BigInt(feeAmount),
|
|
1006
|
+
decimals: asset.decimals
|
|
756
1007
|
})
|
|
757
1008
|
);
|
|
758
1009
|
}
|
|
@@ -768,6 +1019,95 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
|
|
|
768
1019
|
options
|
|
769
1020
|
);
|
|
770
1021
|
}
|
|
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
|
+
var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
|
|
1108
|
+
var RANKING_BUCKET_SIZE_SECS = 60;
|
|
1109
|
+
var COLD_START_BUCKET = -Infinity;
|
|
1110
|
+
var MAX_PAID_CANDIDATES_PER_AGENT = 5;
|
|
771
1111
|
function toDTag(name) {
|
|
772
1112
|
const tag = name.toLowerCase().replace(/[^a-z0-9\s-]/g, (ch) => "_" + ch.charCodeAt(0).toString(16).padStart(2, "0")).replace(/\s+/g, "-").replace(/^-+|-+$/g, "");
|
|
773
1113
|
if (!tag) {
|
|
@@ -775,6 +1115,28 @@ function toDTag(name) {
|
|
|
775
1115
|
}
|
|
776
1116
|
return tag;
|
|
777
1117
|
}
|
|
1118
|
+
function computeRankKey(agent) {
|
|
1119
|
+
const lastPaidJobAt = agent.lastPaidJobAt ?? 0;
|
|
1120
|
+
const total = agent.totalRatingCount ?? 0;
|
|
1121
|
+
const positive = agent.positiveCount ?? 0;
|
|
1122
|
+
const rate = total > 0 ? positive / total : 0;
|
|
1123
|
+
const bucket = lastPaidJobAt > 0 ? Math.floor(lastPaidJobAt / RANKING_BUCKET_SIZE_SECS) * RANKING_BUCKET_SIZE_SECS : COLD_START_BUCKET;
|
|
1124
|
+
return { bucket, rate, lastPaidJobAt, lastSeen: agent.lastSeen };
|
|
1125
|
+
}
|
|
1126
|
+
function compareAgentsByRank(a, b) {
|
|
1127
|
+
const ka = computeRankKey(a);
|
|
1128
|
+
const kb = computeRankKey(b);
|
|
1129
|
+
if (kb.bucket !== ka.bucket) {
|
|
1130
|
+
return kb.bucket - ka.bucket;
|
|
1131
|
+
}
|
|
1132
|
+
if (kb.rate !== ka.rate) {
|
|
1133
|
+
return kb.rate - ka.rate;
|
|
1134
|
+
}
|
|
1135
|
+
if (kb.lastPaidJobAt !== ka.lastPaidJobAt) {
|
|
1136
|
+
return kb.lastPaidJobAt - ka.lastPaidJobAt;
|
|
1137
|
+
}
|
|
1138
|
+
return kb.lastSeen - ka.lastSeen;
|
|
1139
|
+
}
|
|
778
1140
|
function buildAgentsFromEvents(events, network) {
|
|
779
1141
|
const latestByDTag = /* @__PURE__ */ new Map();
|
|
780
1142
|
for (const event of events) {
|
|
@@ -869,9 +1231,37 @@ function buildAgentsFromEvents(events, network) {
|
|
|
869
1231
|
}
|
|
870
1232
|
return agentMap;
|
|
871
1233
|
}
|
|
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
|
+
}
|
|
872
1257
|
var DiscoveryService = class {
|
|
873
|
-
constructor(pool) {
|
|
1258
|
+
constructor(pool, defaultRpc) {
|
|
874
1259
|
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;
|
|
875
1265
|
}
|
|
876
1266
|
/** Count elisym agents (kind:31990 with "elisym" tag). */
|
|
877
1267
|
async fetchAllAgentCount() {
|
|
@@ -959,8 +1349,21 @@ var DiscoveryService = class {
|
|
|
959
1349
|
}
|
|
960
1350
|
return agents;
|
|
961
1351
|
}
|
|
962
|
-
/**
|
|
963
|
-
|
|
1352
|
+
/**
|
|
1353
|
+
* Fetch elisym agents filtered by network, ranked by verified paid-job recency.
|
|
1354
|
+
*
|
|
1355
|
+
* Ranking algorithm:
|
|
1356
|
+
* 1. Bucket each agent into 1-minute slots by `lastPaidJobAt` (last on-chain
|
|
1357
|
+
* verified payment). Cold-start agents (no verified paid job) go into a
|
|
1358
|
+
* sentinel bucket below all populated buckets.
|
|
1359
|
+
* 2. Within a bucket, sort by positive review rate descending.
|
|
1360
|
+
* 3. Tiebreak by raw `lastPaidJobAt`, then `lastSeen` (NIP-89 freshness).
|
|
1361
|
+
*
|
|
1362
|
+
* On-chain verification uses {@link verifyJobPaymentQuick} - one-shot, cached.
|
|
1363
|
+
* If `rpc` is not configured, all agents fall through to cold-start and order
|
|
1364
|
+
* is determined by `lastSeen` only.
|
|
1365
|
+
*/
|
|
1366
|
+
async fetchAgents(network = "devnet", limit, rpcOverride) {
|
|
964
1367
|
const filter = {
|
|
965
1368
|
kinds: [KIND_APP_HANDLER],
|
|
966
1369
|
"#t": ["elisym"]
|
|
@@ -970,40 +1373,100 @@ var DiscoveryService = class {
|
|
|
970
1373
|
}
|
|
971
1374
|
const events = await this.pool.querySync(filter);
|
|
972
1375
|
const agentMap = buildAgentsFromEvents(events, network);
|
|
973
|
-
const agents = Array.from(agentMap.values())
|
|
1376
|
+
const agents = Array.from(agentMap.values());
|
|
974
1377
|
const agentPubkeys = Array.from(agentMap.keys());
|
|
975
|
-
if (agentPubkeys.length
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1378
|
+
if (agentPubkeys.length === 0) {
|
|
1379
|
+
return agents;
|
|
1380
|
+
}
|
|
1381
|
+
const activitySince = Math.floor(Date.now() / 1e3) - RANKING_ACTIVITY_WINDOW_SECS;
|
|
1382
|
+
const resultKinds = /* @__PURE__ */ new Set();
|
|
1383
|
+
for (const agent of agentMap.values()) {
|
|
1384
|
+
for (const k of agent.supportedKinds) {
|
|
1385
|
+
if (k >= KIND_JOB_REQUEST_BASE && k < KIND_JOB_RESULT_BASE) {
|
|
1386
|
+
resultKinds.add(KIND_JOB_RESULT_BASE + (k - KIND_JOB_REQUEST_BASE));
|
|
983
1387
|
}
|
|
984
1388
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1389
|
+
}
|
|
1390
|
+
resultKinds.add(jobResultKind(DEFAULT_KIND_OFFSET));
|
|
1391
|
+
const [resultEvents, feedbackEvents] = await Promise.all([
|
|
1392
|
+
this.pool.queryBatched(
|
|
1393
|
+
{
|
|
1394
|
+
kinds: [...resultKinds],
|
|
1395
|
+
since: activitySince
|
|
1396
|
+
},
|
|
1397
|
+
agentPubkeys
|
|
1398
|
+
),
|
|
1399
|
+
this.pool.queryBatchedByTag(
|
|
1400
|
+
{ kinds: [KIND_JOB_FEEDBACK], since: activitySince },
|
|
1401
|
+
"p",
|
|
1402
|
+
agentPubkeys
|
|
1403
|
+
),
|
|
1404
|
+
this.enrichWithMetadata(agents)
|
|
1405
|
+
]);
|
|
1406
|
+
for (const ev of resultEvents) {
|
|
1407
|
+
if (!nostrTools.verifyEvent(ev)) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
const agent = agentMap.get(ev.pubkey);
|
|
1411
|
+
if (agent && ev.created_at > agent.lastSeen) {
|
|
1412
|
+
agent.lastSeen = ev.created_at;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const paidCandidates = /* @__PURE__ */ new Map();
|
|
1416
|
+
for (const ev of feedbackEvents) {
|
|
1417
|
+
if (!nostrTools.verifyEvent(ev)) {
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
const targetPubkey = ev.tags.find((t) => t[0] === "p")?.[1];
|
|
1421
|
+
if (!targetPubkey) {
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
const agent = agentMap.get(targetPubkey);
|
|
1425
|
+
if (!agent) {
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (ev.created_at > agent.lastSeen) {
|
|
1429
|
+
agent.lastSeen = ev.created_at;
|
|
1430
|
+
}
|
|
1431
|
+
const rating = ev.tags.find((t) => t[0] === "rating")?.[1];
|
|
1432
|
+
if (rating === "1" || rating === "0") {
|
|
1433
|
+
agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
|
|
1434
|
+
if (rating === "1") {
|
|
1435
|
+
agent.positiveCount = (agent.positiveCount ?? 0) + 1;
|
|
999
1436
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1437
|
+
}
|
|
1438
|
+
const status = ev.tags.find((t) => t[0] === "status")?.[1];
|
|
1439
|
+
const txTag = ev.tags.find((t) => t[0] === "tx");
|
|
1440
|
+
const txSignature = txTag?.[1];
|
|
1441
|
+
if (status === "payment-completed" && typeof txSignature === "string" && txSignature) {
|
|
1442
|
+
const list = paidCandidates.get(targetPubkey);
|
|
1443
|
+
const candidate = { txSignature, createdAt: ev.created_at };
|
|
1444
|
+
if (list) {
|
|
1445
|
+
list.push(candidate);
|
|
1446
|
+
} else {
|
|
1447
|
+
paidCandidates.set(targetPubkey, [candidate]);
|
|
1003
1448
|
}
|
|
1004
1449
|
}
|
|
1005
|
-
agents.sort((a, b) => b.lastSeen - a.lastSeen);
|
|
1006
1450
|
}
|
|
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
|
+
agents.sort(compareAgentsByRank);
|
|
1007
1470
|
return agents;
|
|
1008
1471
|
}
|
|
1009
1472
|
/**
|
|
@@ -2351,7 +2814,7 @@ var ElisymClient = class {
|
|
|
2351
2814
|
payment;
|
|
2352
2815
|
constructor(config = {}) {
|
|
2353
2816
|
this.pool = new NostrPool(config.relays ?? RELAYS);
|
|
2354
|
-
this.discovery = new DiscoveryService(this.pool);
|
|
2817
|
+
this.discovery = new DiscoveryService(this.pool, config.rpc);
|
|
2355
2818
|
this.marketplace = new MarketplaceService(this.pool);
|
|
2356
2819
|
this.ping = new PingService(this.pool);
|
|
2357
2820
|
this.media = new MediaService(config.uploadUrl);
|
|
@@ -2361,96 +2824,124 @@ var ElisymClient = class {
|
|
|
2361
2824
|
this.pool.close();
|
|
2362
2825
|
}
|
|
2363
2826
|
};
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
var
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
)
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
}
|
|
2406
|
-
const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
|
|
2407
|
-
if (fracPart.length > asset.decimals) {
|
|
2408
|
-
throw new Error(
|
|
2409
|
-
`${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
|
|
2410
|
-
);
|
|
2411
|
-
}
|
|
2412
|
-
const unit = 10n ** BigInt(asset.decimals);
|
|
2413
|
-
const whole = BigInt(wholePart);
|
|
2414
|
-
const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
|
|
2415
|
-
const raw = whole * unit + frac;
|
|
2416
|
-
if (raw === 0n) {
|
|
2417
|
-
throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
|
|
2418
|
-
}
|
|
2419
|
-
if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
2420
|
-
throw new Error(
|
|
2421
|
-
`${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
|
|
2422
|
-
);
|
|
2827
|
+
var DEFAULT_COMPUTE_UNIT_LIMIT2 = 2e5;
|
|
2828
|
+
var DEFAULT_PRIORITY_FEE_PERCENTILE2 = 75;
|
|
2829
|
+
var BASE_FEE_LAMPORTS_PER_SIGNATURE = 5000n;
|
|
2830
|
+
var FALLBACK_ATA_RENT_LAMPORTS = 2039280n;
|
|
2831
|
+
var SPL_TOKEN_ACCOUNT_SIZE = 165;
|
|
2832
|
+
async function estimateSolFeeLamports(rpc, paymentRequest, _payerAddress, options) {
|
|
2833
|
+
const numSignatures = options?.numSignatures ?? 1;
|
|
2834
|
+
const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT2;
|
|
2835
|
+
const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
|
|
2836
|
+
percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE2
|
|
2837
|
+
});
|
|
2838
|
+
const baseFeeLamports = BASE_FEE_LAMPORTS_PER_SIGNATURE * BigInt(numSignatures);
|
|
2839
|
+
const priorityFeeLamports = ceilDiv(
|
|
2840
|
+
priorityFeeMicroLamports * BigInt(computeUnitLimit),
|
|
2841
|
+
1000000n
|
|
2842
|
+
);
|
|
2843
|
+
const asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
2844
|
+
let rentLamports = 0n;
|
|
2845
|
+
let rentPerAtaLamports = 0n;
|
|
2846
|
+
let missingAtaCount = 0;
|
|
2847
|
+
if (asset.mint) {
|
|
2848
|
+
rentPerAtaLamports = await fetchAtaRent(rpc);
|
|
2849
|
+
const mint = kit.address(asset.mint);
|
|
2850
|
+
const ataAccountsToCheck = [];
|
|
2851
|
+
const [recipientAta] = await token.findAssociatedTokenPda({
|
|
2852
|
+
owner: kit.address(paymentRequest.recipient),
|
|
2853
|
+
tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
|
|
2854
|
+
mint
|
|
2855
|
+
});
|
|
2856
|
+
ataAccountsToCheck.push(recipientAta);
|
|
2857
|
+
const feeAmount = paymentRequest.fee_amount ?? 0;
|
|
2858
|
+
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
2859
|
+
const [treasuryAta] = await token.findAssociatedTokenPda({
|
|
2860
|
+
owner: kit.address(paymentRequest.fee_address),
|
|
2861
|
+
tokenProgram: token.TOKEN_PROGRAM_ADDRESS,
|
|
2862
|
+
mint
|
|
2863
|
+
});
|
|
2864
|
+
ataAccountsToCheck.push(treasuryAta);
|
|
2865
|
+
}
|
|
2866
|
+
missingAtaCount = await countMissingAccounts(rpc, ataAccountsToCheck);
|
|
2867
|
+
rentLamports = rentPerAtaLamports * BigInt(missingAtaCount);
|
|
2423
2868
|
}
|
|
2424
|
-
|
|
2869
|
+
const totalLamports = baseFeeLamports + priorityFeeLamports + rentLamports;
|
|
2870
|
+
return {
|
|
2871
|
+
baseFeeLamports,
|
|
2872
|
+
priorityFeeLamports,
|
|
2873
|
+
rentLamports,
|
|
2874
|
+
totalLamports,
|
|
2875
|
+
breakdown: {
|
|
2876
|
+
numSignatures,
|
|
2877
|
+
priorityFeeMicroLamports,
|
|
2878
|
+
computeUnitLimit,
|
|
2879
|
+
rentPerAtaLamports,
|
|
2880
|
+
missingAtaCount
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2425
2883
|
}
|
|
2426
|
-
function
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2884
|
+
async function fetchAtaRent(rpc) {
|
|
2885
|
+
try {
|
|
2886
|
+
const lamports = await rpc.getMinimumBalanceForRentExemption(BigInt(SPL_TOKEN_ACCOUNT_SIZE)).send();
|
|
2887
|
+
if (typeof lamports === "bigint") {
|
|
2888
|
+
return lamports;
|
|
2889
|
+
}
|
|
2890
|
+
if (typeof lamports === "number" && Number.isFinite(lamports) && lamports > 0) {
|
|
2891
|
+
return BigInt(lamports);
|
|
2892
|
+
}
|
|
2893
|
+
return FALLBACK_ATA_RENT_LAMPORTS;
|
|
2894
|
+
} catch {
|
|
2895
|
+
return FALLBACK_ATA_RENT_LAMPORTS;
|
|
2434
2896
|
}
|
|
2435
|
-
return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
|
|
2436
2897
|
}
|
|
2437
|
-
async function
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
} catch (e) {
|
|
2898
|
+
async function countMissingAccounts(rpc, accounts) {
|
|
2899
|
+
if (accounts.length === 0) {
|
|
2900
|
+
return 0;
|
|
2901
|
+
}
|
|
2902
|
+
let missing = 0;
|
|
2903
|
+
for (const acct of accounts) {
|
|
2444
2904
|
try {
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2905
|
+
const res = await rpc.getAccountInfo(acct, { encoding: "base64" }).send();
|
|
2906
|
+
if (!res || !res.value) {
|
|
2907
|
+
missing++;
|
|
2908
|
+
}
|
|
2447
2909
|
} catch {
|
|
2910
|
+
missing++;
|
|
2448
2911
|
}
|
|
2449
|
-
throw e;
|
|
2450
2912
|
}
|
|
2913
|
+
return missing;
|
|
2914
|
+
}
|
|
2915
|
+
function ceilDiv(num, denom) {
|
|
2916
|
+
if (denom === 0n) {
|
|
2917
|
+
throw new Error("division by zero in ceilDiv");
|
|
2918
|
+
}
|
|
2919
|
+
const q = num / denom;
|
|
2920
|
+
const r = num % denom;
|
|
2921
|
+
return r === 0n ? q : q + 1n;
|
|
2922
|
+
}
|
|
2923
|
+
function formatFeeBreakdown(estimate) {
|
|
2924
|
+
const line = (label, lamports) => {
|
|
2925
|
+
const label16 = label.padEnd(14);
|
|
2926
|
+
return ` ${label16}${lamports.toString()} lamports (${lamportsToSol(lamports)} SOL)`;
|
|
2927
|
+
};
|
|
2928
|
+
const lines = [
|
|
2929
|
+
"Estimated SOL cost for this transaction:",
|
|
2930
|
+
line("Base fee:", estimate.baseFeeLamports),
|
|
2931
|
+
line("Priority fee:", estimate.priorityFeeLamports)
|
|
2932
|
+
];
|
|
2933
|
+
if (estimate.rentLamports > 0n) {
|
|
2934
|
+
lines.push(line("ATA rent:", estimate.rentLamports));
|
|
2935
|
+
}
|
|
2936
|
+
lines.push(line("Total:", estimate.totalLamports));
|
|
2937
|
+
return lines.join("\n");
|
|
2938
|
+
}
|
|
2939
|
+
function lamportsToSol(lamports) {
|
|
2940
|
+
const LAMPORTS_PER_SOL2 = 1000000000n;
|
|
2941
|
+
const whole = lamports / LAMPORTS_PER_SOL2;
|
|
2942
|
+
const frac = lamports % LAMPORTS_PER_SOL2;
|
|
2943
|
+
return `${whole}.${frac.toString().padStart(9, "0")}`;
|
|
2451
2944
|
}
|
|
2452
|
-
|
|
2453
|
-
// src/config/global.ts
|
|
2454
2945
|
var SessionSpendLimitEntrySchema = zod.z.object({
|
|
2455
2946
|
chain: zod.z.enum(["solana"]),
|
|
2456
2947
|
token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
|
|
@@ -2460,30 +2951,6 @@ var SessionSpendLimitEntrySchema = zod.z.object({
|
|
|
2460
2951
|
var GlobalConfigSchema = zod.z.object({
|
|
2461
2952
|
session_spend_limits: zod.z.array(SessionSpendLimitEntrySchema).max(16).optional()
|
|
2462
2953
|
}).strict();
|
|
2463
|
-
function isEnoent(e) {
|
|
2464
|
-
return typeof e === "object" && e !== null && "code" in e && e.code === "ENOENT";
|
|
2465
|
-
}
|
|
2466
|
-
async function loadGlobalConfig(path) {
|
|
2467
|
-
let raw;
|
|
2468
|
-
try {
|
|
2469
|
-
raw = await promises.readFile(path, "utf-8");
|
|
2470
|
-
} catch (e) {
|
|
2471
|
-
if (isEnoent(e)) {
|
|
2472
|
-
return {};
|
|
2473
|
-
}
|
|
2474
|
-
throw e;
|
|
2475
|
-
}
|
|
2476
|
-
if (raw.trim() === "") {
|
|
2477
|
-
return {};
|
|
2478
|
-
}
|
|
2479
|
-
const parsed = YAML2__default.default.parse(raw);
|
|
2480
|
-
return GlobalConfigSchema.parse(parsed ?? {});
|
|
2481
|
-
}
|
|
2482
|
-
async function writeGlobalConfig(path, config) {
|
|
2483
|
-
const validated = GlobalConfigSchema.parse(config);
|
|
2484
|
-
const body = YAML2__default.default.stringify(validated);
|
|
2485
|
-
await writeFileAtomic(path, body, 420);
|
|
2486
|
-
}
|
|
2487
2954
|
function formatSol(lamports) {
|
|
2488
2955
|
const sol = new Decimal2__default.default(lamports).div(LAMPORTS_PER_SOL);
|
|
2489
2956
|
if (sol.gte(1e6)) {
|
|
@@ -2708,6 +3175,7 @@ exports.RELAYS = RELAYS;
|
|
|
2708
3175
|
exports.SECRET_REDACT_PATHS = SECRET_REDACT_PATHS;
|
|
2709
3176
|
exports.SessionSpendLimitEntrySchema = SessionSpendLimitEntrySchema;
|
|
2710
3177
|
exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
|
|
3178
|
+
exports.USDC_SOLANA_DEVNET = USDC_SOLANA_DEVNET;
|
|
2711
3179
|
exports.assertExpiry = assertExpiry;
|
|
2712
3180
|
exports.assertLamports = assertLamports;
|
|
2713
3181
|
exports.assetByKey = assetByKey;
|
|
@@ -2716,28 +3184,33 @@ exports.buildPaymentInstructions = buildPaymentInstructions;
|
|
|
2716
3184
|
exports.calculateProtocolFee = calculateProtocolFee;
|
|
2717
3185
|
exports.clearPriorityFeeCache = clearPriorityFeeCache;
|
|
2718
3186
|
exports.clearProtocolConfigCache = clearProtocolConfigCache;
|
|
3187
|
+
exports.clearQuickVerifyCache = clearQuickVerifyCache;
|
|
3188
|
+
exports.compareAgentsByRank = compareAgentsByRank;
|
|
3189
|
+
exports.computeRankKey = computeRankKey;
|
|
2719
3190
|
exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
|
|
2720
3191
|
exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
|
|
2721
3192
|
exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
|
|
3193
|
+
exports.estimateSolFeeLamports = estimateSolFeeLamports;
|
|
2722
3194
|
exports.formatAssetAmount = formatAssetAmount;
|
|
3195
|
+
exports.formatFeeBreakdown = formatFeeBreakdown;
|
|
2723
3196
|
exports.formatSol = formatSol;
|
|
2724
3197
|
exports.getProtocolConfig = getProtocolConfig;
|
|
2725
3198
|
exports.getProtocolProgramId = getProtocolProgramId;
|
|
2726
3199
|
exports.jobRequestKind = jobRequestKind;
|
|
2727
3200
|
exports.jobResultKind = jobResultKind;
|
|
2728
|
-
exports.loadGlobalConfig = loadGlobalConfig;
|
|
2729
3201
|
exports.makeCensor = makeCensor;
|
|
2730
3202
|
exports.nip44Decrypt = nip44Decrypt;
|
|
2731
3203
|
exports.nip44Encrypt = nip44Encrypt;
|
|
2732
3204
|
exports.parseAssetAmount = parseAssetAmount;
|
|
2733
3205
|
exports.parsePaymentRequest = parsePaymentRequest;
|
|
2734
3206
|
exports.pickPercentileFee = pickPercentileFee;
|
|
3207
|
+
exports.resolveAssetFromPaymentRequest = resolveAssetFromPaymentRequest;
|
|
2735
3208
|
exports.resolveKnownAsset = resolveKnownAsset;
|
|
2736
3209
|
exports.timeAgo = timeAgo;
|
|
2737
3210
|
exports.toDTag = toDTag;
|
|
2738
3211
|
exports.truncateKey = truncateKey;
|
|
2739
3212
|
exports.validateAgentName = validateAgentName;
|
|
2740
3213
|
exports.validateExpiry = validateExpiry;
|
|
2741
|
-
exports.
|
|
3214
|
+
exports.verifyJobPaymentQuick = verifyJobPaymentQuick;
|
|
2742
3215
|
//# sourceMappingURL=index.cjs.map
|
|
2743
3216
|
//# sourceMappingURL=index.cjs.map
|