@elisym/sdk 0.8.0 → 0.9.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 +14 -16
- package/dist/agent-store.d.ts +14 -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 +394 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -118
- package/dist/index.d.ts +151 -118
- package/dist/index.js +391 -135
- 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.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { getTransferSolInstruction } from '@solana-program/system';
|
|
2
|
+
import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, getCreateAssociatedTokenIdempotentInstruction, ASSOCIATED_TOKEN_PROGRAM_ADDRESS, getTransferCheckedInstruction } from '@solana-program/token';
|
|
2
3
|
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageComputeUnitLimit, setTransactionMessageComputeUnitPrice, appendTransactionMessageInstructions, signTransactionMessageWithSigners, address, AccountRole, getProgramDerivedAddress, assertAccountExists, isAddress, getAddressDecoder, fetchEncodedAccount, decodeAccount, getStructDecoder, fixDecoderSize, getBytesDecoder, getU8Decoder, getOptionDecoder, getU16Decoder, getBooleanDecoder, getI64Decoder } from '@solana/kit';
|
|
3
4
|
import Decimal2 from 'decimal.js-light';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
import { verifyEvent, finalizeEvent, getPublicKey, nip19, generateSecretKey, SimplePool } from 'nostr-tools';
|
|
6
7
|
import * as nip44 from 'nostr-tools/nip44';
|
|
7
|
-
import { readFile, mkdir, writeFile, rename } from 'node:fs/promises';
|
|
8
|
-
import YAML2 from 'yaml';
|
|
9
|
-
import { randomBytes } from 'node:crypto';
|
|
10
|
-
import { dirname } from 'node:path';
|
|
11
8
|
|
|
12
9
|
// src/constants.ts
|
|
13
10
|
var RELAYS = [
|
|
@@ -97,13 +94,13 @@ function decodeConfig(encodedAccount) {
|
|
|
97
94
|
getConfigDecoder()
|
|
98
95
|
);
|
|
99
96
|
}
|
|
100
|
-
async function fetchConfig(rpc,
|
|
101
|
-
const maybeAccount = await fetchMaybeConfig(rpc,
|
|
97
|
+
async function fetchConfig(rpc, address3, config) {
|
|
98
|
+
const maybeAccount = await fetchMaybeConfig(rpc, address3, config);
|
|
102
99
|
assertAccountExists(maybeAccount);
|
|
103
100
|
return maybeAccount;
|
|
104
101
|
}
|
|
105
|
-
async function fetchMaybeConfig(rpc,
|
|
106
|
-
const maybeAccount = await fetchEncodedAccount(rpc,
|
|
102
|
+
async function fetchMaybeConfig(rpc, address3, config) {
|
|
103
|
+
const maybeAccount = await fetchEncodedAccount(rpc, address3, config);
|
|
107
104
|
return decodeConfig(maybeAccount);
|
|
108
105
|
}
|
|
109
106
|
if (process.env.NODE_ENV !== "production") ;
|
|
@@ -154,6 +151,99 @@ async function getProtocolConfig(rpc, programId, options) {
|
|
|
154
151
|
);
|
|
155
152
|
}
|
|
156
153
|
}
|
|
154
|
+
|
|
155
|
+
// src/payment/assets.ts
|
|
156
|
+
var NATIVE_SOL = {
|
|
157
|
+
chain: "solana",
|
|
158
|
+
token: "sol",
|
|
159
|
+
decimals: 9,
|
|
160
|
+
symbol: "SOL"
|
|
161
|
+
};
|
|
162
|
+
var USDC_SOLANA_DEVNET = {
|
|
163
|
+
chain: "solana",
|
|
164
|
+
token: "usdc",
|
|
165
|
+
mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
166
|
+
decimals: 6,
|
|
167
|
+
symbol: "USDC"
|
|
168
|
+
};
|
|
169
|
+
var KNOWN_ASSETS = [NATIVE_SOL, USDC_SOLANA_DEVNET];
|
|
170
|
+
function assetKey(a) {
|
|
171
|
+
return a.mint ? `${a.chain}:${a.token}:${a.mint}` : `${a.chain}:${a.token}`;
|
|
172
|
+
}
|
|
173
|
+
function resolveKnownAsset(chain, token, mint) {
|
|
174
|
+
const key = mint ? `${chain}:${token}:${mint}` : `${chain}:${token}`;
|
|
175
|
+
return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
|
|
176
|
+
}
|
|
177
|
+
function assetByKey(key) {
|
|
178
|
+
return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
|
|
179
|
+
}
|
|
180
|
+
function resolveAssetFromPaymentRequest(request) {
|
|
181
|
+
if (!request.asset) {
|
|
182
|
+
return NATIVE_SOL;
|
|
183
|
+
}
|
|
184
|
+
const found = resolveKnownAsset(request.asset.chain, request.asset.token, request.asset.mint);
|
|
185
|
+
if (!found) {
|
|
186
|
+
const display = request.asset.mint ? `${request.asset.chain}:${request.asset.token}:${request.asset.mint}` : `${request.asset.chain}:${request.asset.token}`;
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Unknown asset in payment request: ${display}. Known assets: ${KNOWN_ASSETS.map(assetKey).join(", ")}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
var DECIMAL_RE = /^(\d+\.\d*|\d*\.\d+|\d+)$/;
|
|
194
|
+
function parseAssetAmount(asset, human) {
|
|
195
|
+
const trimmed = human.trim();
|
|
196
|
+
if (!trimmed) {
|
|
197
|
+
throw new Error(`${asset.symbol} amount is empty`);
|
|
198
|
+
}
|
|
199
|
+
if (trimmed.startsWith("-")) {
|
|
200
|
+
throw new Error(`${asset.symbol} amount cannot be negative`);
|
|
201
|
+
}
|
|
202
|
+
if (!DECIMAL_RE.test(trimmed)) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`${asset.symbol} amount must be a non-negative decimal (e.g. "0.5", "1"); got "${human}"`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const dotPos = trimmed.indexOf(".");
|
|
208
|
+
let wholePart;
|
|
209
|
+
if (dotPos === -1) {
|
|
210
|
+
wholePart = trimmed;
|
|
211
|
+
} else if (dotPos === 0) {
|
|
212
|
+
wholePart = "0";
|
|
213
|
+
} else {
|
|
214
|
+
wholePart = trimmed.slice(0, dotPos);
|
|
215
|
+
}
|
|
216
|
+
const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
|
|
217
|
+
if (fracPart.length > asset.decimals) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const unit = 10n ** BigInt(asset.decimals);
|
|
223
|
+
const whole = BigInt(wholePart);
|
|
224
|
+
const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
|
|
225
|
+
const raw = whole * unit + frac;
|
|
226
|
+
if (raw === 0n) {
|
|
227
|
+
throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
|
|
228
|
+
}
|
|
229
|
+
if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return raw;
|
|
235
|
+
}
|
|
236
|
+
function formatAssetAmount(asset, raw) {
|
|
237
|
+
const sign = raw < 0n ? "-" : "";
|
|
238
|
+
const abs = raw < 0n ? -raw : raw;
|
|
239
|
+
const unit = 10n ** BigInt(asset.decimals);
|
|
240
|
+
const whole = abs / unit;
|
|
241
|
+
const frac = abs % unit;
|
|
242
|
+
if (asset.decimals === 0) {
|
|
243
|
+
return `${sign}${whole} ${asset.symbol}`;
|
|
244
|
+
}
|
|
245
|
+
return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
|
|
246
|
+
}
|
|
157
247
|
var BPS_DENOMINATOR = 1e4;
|
|
158
248
|
function assertLamports(value, field) {
|
|
159
249
|
if (!Number.isInteger(value) || value < 0) {
|
|
@@ -263,6 +353,12 @@ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
|
|
|
263
353
|
var lamportsSchema = z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
|
|
264
354
|
var feeAmountSchema = z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
|
|
265
355
|
var solanaAddressSchema = z.string().regex(BASE58_RE, "must be base58").regex(SOLANA_ADDRESS_LENGTH_RE, "must be 32-44 base58 chars");
|
|
356
|
+
var paymentAssetRefSchema = z.object({
|
|
357
|
+
chain: z.string().min(1),
|
|
358
|
+
token: z.string().min(1),
|
|
359
|
+
mint: solanaAddressSchema.optional(),
|
|
360
|
+
decimals: z.number().int().min(0).max(18)
|
|
361
|
+
});
|
|
266
362
|
var PaymentRequestSchema = z.object({
|
|
267
363
|
recipient: solanaAddressSchema,
|
|
268
364
|
amount: lamportsSchema,
|
|
@@ -271,7 +367,8 @@ var PaymentRequestSchema = z.object({
|
|
|
271
367
|
fee_address: solanaAddressSchema.optional(),
|
|
272
368
|
fee_amount: feeAmountSchema.optional(),
|
|
273
369
|
created_at: z.number().int().positive(),
|
|
274
|
-
expiry_secs: z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`)
|
|
370
|
+
expiry_secs: z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`),
|
|
371
|
+
asset: paymentAssetRefSchema.optional()
|
|
275
372
|
});
|
|
276
373
|
function parsePaymentRequest(input, options) {
|
|
277
374
|
let parsed;
|
|
@@ -353,6 +450,12 @@ var SolanaPaymentStrategy = class {
|
|
|
353
450
|
assertExpirySecs(expirySecs);
|
|
354
451
|
const feeAmount = calculateProtocolFee(amount, config.feeBps);
|
|
355
452
|
const reference = generateReference();
|
|
453
|
+
const assetRef = options?.asset && options.asset !== NATIVE_SOL ? {
|
|
454
|
+
chain: options.asset.chain,
|
|
455
|
+
token: options.asset.token,
|
|
456
|
+
mint: options.asset.mint,
|
|
457
|
+
decimals: options.asset.decimals
|
|
458
|
+
} : void 0;
|
|
356
459
|
return {
|
|
357
460
|
recipient: recipientAddress,
|
|
358
461
|
amount,
|
|
@@ -360,7 +463,8 @@ var SolanaPaymentStrategy = class {
|
|
|
360
463
|
fee_address: config.treasury,
|
|
361
464
|
fee_amount: feeAmount,
|
|
362
465
|
created_at: Math.floor(Date.now() / 1e3),
|
|
363
|
-
expiry_secs: expirySecs
|
|
466
|
+
expiry_secs: expirySecs,
|
|
467
|
+
...assetRef ? { asset: assetRef } : {}
|
|
364
468
|
};
|
|
365
469
|
}
|
|
366
470
|
validatePaymentRequest(requestJson, config, expectedRecipient, options) {
|
|
@@ -378,6 +482,16 @@ var SolanaPaymentStrategy = class {
|
|
|
378
482
|
return { code: "invalid_amount", message: parsed.error.message };
|
|
379
483
|
}
|
|
380
484
|
const data = parsed.data;
|
|
485
|
+
if (data.asset) {
|
|
486
|
+
try {
|
|
487
|
+
resolveAssetFromPaymentRequest(data);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
return {
|
|
490
|
+
code: "invalid_asset",
|
|
491
|
+
message: error instanceof Error ? error.message : String(error)
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
381
495
|
if (!isValidSolanaAddress(data.recipient)) {
|
|
382
496
|
return {
|
|
383
497
|
code: "invalid_recipient_address",
|
|
@@ -466,7 +580,7 @@ var SolanaPaymentStrategy = class {
|
|
|
466
580
|
if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
|
|
467
581
|
throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
|
|
468
582
|
}
|
|
469
|
-
const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
|
|
583
|
+
const paymentInstructions = await buildPaymentInstructions(paymentRequest, payerSigner);
|
|
470
584
|
const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
|
|
471
585
|
percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
|
|
472
586
|
});
|
|
@@ -528,6 +642,16 @@ var SolanaPaymentStrategy = class {
|
|
|
528
642
|
error: `Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount})`
|
|
529
643
|
};
|
|
530
644
|
}
|
|
645
|
+
let asset;
|
|
646
|
+
try {
|
|
647
|
+
asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
return {
|
|
650
|
+
verified: false,
|
|
651
|
+
error: error instanceof Error ? error.message : String(error)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const mint = asset.mint;
|
|
531
655
|
if (options?.txSignature) {
|
|
532
656
|
return this._verifyBySignature(
|
|
533
657
|
rpc,
|
|
@@ -537,6 +661,7 @@ var SolanaPaymentStrategy = class {
|
|
|
537
661
|
treasury,
|
|
538
662
|
expectedNet,
|
|
539
663
|
feeAmount,
|
|
664
|
+
mint,
|
|
540
665
|
options?.retries ?? DEFAULTS.VERIFY_RETRIES,
|
|
541
666
|
options?.intervalMs ?? DEFAULTS.VERIFY_INTERVAL_MS
|
|
542
667
|
);
|
|
@@ -548,11 +673,12 @@ var SolanaPaymentStrategy = class {
|
|
|
548
673
|
treasury,
|
|
549
674
|
expectedNet,
|
|
550
675
|
feeAmount,
|
|
676
|
+
mint,
|
|
551
677
|
options?.retries ?? DEFAULTS.VERIFY_BY_REF_RETRIES,
|
|
552
678
|
options?.intervalMs ?? DEFAULTS.VERIFY_BY_REF_INTERVAL_MS
|
|
553
679
|
);
|
|
554
680
|
}
|
|
555
|
-
async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
|
|
681
|
+
async _verifyBySignature(rpc, txSignature, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
|
|
556
682
|
let lastError;
|
|
557
683
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
558
684
|
try {
|
|
@@ -571,15 +697,18 @@ var SolanaPaymentStrategy = class {
|
|
|
571
697
|
error: tx?.meta?.err ? "Transaction failed on-chain" : "Transaction not found"
|
|
572
698
|
};
|
|
573
699
|
}
|
|
574
|
-
const verdict =
|
|
700
|
+
const verdict = checkTxDiff({
|
|
575
701
|
accountKeys: tx.transaction.message.accountKeys,
|
|
576
702
|
preBalances: tx.meta.preBalances,
|
|
577
703
|
postBalances: tx.meta.postBalances,
|
|
704
|
+
preTokenBalances: tx.meta.preTokenBalances,
|
|
705
|
+
postTokenBalances: tx.meta.postTokenBalances,
|
|
578
706
|
referenceKey,
|
|
579
707
|
recipientAddress,
|
|
580
708
|
treasuryAddress,
|
|
581
709
|
expectedNet,
|
|
582
|
-
expectedFee
|
|
710
|
+
expectedFee,
|
|
711
|
+
mint
|
|
583
712
|
});
|
|
584
713
|
if (verdict.ok) {
|
|
585
714
|
return { verified: true, txSignature };
|
|
@@ -597,7 +726,7 @@ var SolanaPaymentStrategy = class {
|
|
|
597
726
|
error: `Verification failed after ${retries} retries: ${lastError instanceof Error ? lastError.message : "unknown error"}`
|
|
598
727
|
};
|
|
599
728
|
}
|
|
600
|
-
async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, retries, intervalMs) {
|
|
729
|
+
async _verifyByReference(rpc, referenceKey, recipientAddress, treasuryAddress, expectedNet, expectedFee, mint, retries, intervalMs) {
|
|
601
730
|
let lastError;
|
|
602
731
|
const reference = address(referenceKey);
|
|
603
732
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
@@ -621,15 +750,18 @@ var SolanaPaymentStrategy = class {
|
|
|
621
750
|
if (!tx?.meta || tx.meta.err) {
|
|
622
751
|
continue;
|
|
623
752
|
}
|
|
624
|
-
const verdict =
|
|
753
|
+
const verdict = checkTxDiff({
|
|
625
754
|
accountKeys: tx.transaction.message.accountKeys,
|
|
626
755
|
preBalances: tx.meta.preBalances,
|
|
627
756
|
postBalances: tx.meta.postBalances,
|
|
757
|
+
preTokenBalances: tx.meta.preTokenBalances,
|
|
758
|
+
postTokenBalances: tx.meta.postTokenBalances,
|
|
628
759
|
referenceKey,
|
|
629
760
|
recipientAddress,
|
|
630
761
|
treasuryAddress,
|
|
631
762
|
expectedNet,
|
|
632
|
-
expectedFee
|
|
763
|
+
expectedFee,
|
|
764
|
+
mint
|
|
633
765
|
});
|
|
634
766
|
if (verdict.ok) {
|
|
635
767
|
return { verified: true, txSignature: sig };
|
|
@@ -649,7 +781,7 @@ var SolanaPaymentStrategy = class {
|
|
|
649
781
|
};
|
|
650
782
|
}
|
|
651
783
|
};
|
|
652
|
-
function
|
|
784
|
+
function checkTxDiff(input) {
|
|
653
785
|
const balanceCount = input.preBalances.length;
|
|
654
786
|
const keyToIdx = /* @__PURE__ */ new Map();
|
|
655
787
|
for (let i = 0; i < Math.min(input.accountKeys.length, balanceCount); i++) {
|
|
@@ -661,6 +793,9 @@ function checkBalanceDiff(input) {
|
|
|
661
793
|
if (!keyToIdx.has(input.referenceKey)) {
|
|
662
794
|
return { ok: false, reason: "Reference key not found in transaction - possible replay" };
|
|
663
795
|
}
|
|
796
|
+
if (input.mint) {
|
|
797
|
+
return checkTokenBalanceDiff(input);
|
|
798
|
+
}
|
|
664
799
|
const recipientIdx = keyToIdx.get(input.recipientAddress);
|
|
665
800
|
if (recipientIdx === void 0) {
|
|
666
801
|
return { ok: false, reason: "Recipient not found in transaction" };
|
|
@@ -693,6 +828,47 @@ function checkBalanceDiff(input) {
|
|
|
693
828
|
}
|
|
694
829
|
return { ok: true };
|
|
695
830
|
}
|
|
831
|
+
function checkTokenBalanceDiff(input) {
|
|
832
|
+
const mint = input.mint;
|
|
833
|
+
if (!mint) {
|
|
834
|
+
return { ok: false, reason: "Expected mint for SPL verification, got none" };
|
|
835
|
+
}
|
|
836
|
+
const pre = input.preTokenBalances ?? [];
|
|
837
|
+
const post = input.postTokenBalances ?? [];
|
|
838
|
+
const tokenDelta = (ownerAddress) => {
|
|
839
|
+
const preEntry = pre.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
|
|
840
|
+
const postEntry = post.find((entry) => entry.owner === ownerAddress && entry.mint === mint);
|
|
841
|
+
if (!postEntry) {
|
|
842
|
+
return -1n;
|
|
843
|
+
}
|
|
844
|
+
const preAmount = preEntry ? BigInt(preEntry.uiTokenAmount.amount) : 0n;
|
|
845
|
+
const postAmount = BigInt(postEntry.uiTokenAmount.amount);
|
|
846
|
+
return postAmount - preAmount;
|
|
847
|
+
};
|
|
848
|
+
const recipientDelta = tokenDelta(input.recipientAddress);
|
|
849
|
+
if (recipientDelta === -1n) {
|
|
850
|
+
return { ok: false, reason: "Recipient token account not found in transaction" };
|
|
851
|
+
}
|
|
852
|
+
if (recipientDelta < BigInt(input.expectedNet)) {
|
|
853
|
+
return {
|
|
854
|
+
ok: false,
|
|
855
|
+
reason: `Recipient received ${recipientDelta.toString()} tokens, expected >= ${input.expectedNet}`
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
if (input.expectedFee > 0) {
|
|
859
|
+
const treasuryDelta = tokenDelta(input.treasuryAddress);
|
|
860
|
+
if (treasuryDelta === -1n) {
|
|
861
|
+
return { ok: false, reason: "Treasury token account not found in transaction" };
|
|
862
|
+
}
|
|
863
|
+
if (treasuryDelta < BigInt(input.expectedFee)) {
|
|
864
|
+
return {
|
|
865
|
+
ok: false,
|
|
866
|
+
reason: `Treasury received ${treasuryDelta.toString()} tokens, expected >= ${input.expectedFee}`
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return { ok: true };
|
|
871
|
+
}
|
|
696
872
|
function bigIntDelta(post, pre) {
|
|
697
873
|
const postValue = post === void 0 ? 0n : BigInt(post);
|
|
698
874
|
const preValue = pre === void 0 ? 0n : BigInt(pre);
|
|
@@ -701,7 +877,7 @@ function bigIntDelta(post, pre) {
|
|
|
701
877
|
function waitMs(ms) {
|
|
702
878
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
703
879
|
}
|
|
704
|
-
function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
880
|
+
async function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
705
881
|
const recipient = address(paymentRequest.recipient);
|
|
706
882
|
const reference = address(paymentRequest.reference);
|
|
707
883
|
const feeAmount = paymentRequest.fee_amount ?? 0;
|
|
@@ -711,22 +887,98 @@ function buildPaymentInstructions(paymentRequest, payerSigner) {
|
|
|
711
887
|
`Fee amount (${feeAmount}) exceeds or equals total amount (${paymentRequest.amount}). Cannot create transaction with non-positive provider amount.`
|
|
712
888
|
);
|
|
713
889
|
}
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
890
|
+
const asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
891
|
+
if (!asset.mint) {
|
|
892
|
+
const providerTransferIx2 = getTransferSolInstruction({
|
|
893
|
+
source: payerSigner,
|
|
894
|
+
destination: recipient,
|
|
895
|
+
amount: BigInt(providerAmount)
|
|
896
|
+
});
|
|
897
|
+
const providerTransferIxWithReference2 = {
|
|
898
|
+
...providerTransferIx2,
|
|
899
|
+
accounts: [
|
|
900
|
+
...providerTransferIx2.accounts,
|
|
901
|
+
{ address: reference, role: AccountRole.READONLY }
|
|
902
|
+
]
|
|
903
|
+
};
|
|
904
|
+
const instructions2 = [providerTransferIxWithReference2];
|
|
905
|
+
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
906
|
+
instructions2.push(
|
|
907
|
+
getTransferSolInstruction({
|
|
908
|
+
source: payerSigner,
|
|
909
|
+
destination: address(paymentRequest.fee_address),
|
|
910
|
+
amount: BigInt(feeAmount)
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
return instructions2;
|
|
915
|
+
}
|
|
916
|
+
const mint = address(asset.mint);
|
|
917
|
+
const payerAddress = payerSigner.address;
|
|
918
|
+
const [payerAta] = await findAssociatedTokenPda({
|
|
919
|
+
owner: payerAddress,
|
|
920
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
921
|
+
mint
|
|
922
|
+
});
|
|
923
|
+
const [recipientAta] = await findAssociatedTokenPda({
|
|
924
|
+
owner: recipient,
|
|
925
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
926
|
+
mint
|
|
927
|
+
});
|
|
928
|
+
const instructions = [];
|
|
929
|
+
instructions.push(
|
|
930
|
+
getCreateAssociatedTokenIdempotentInstruction(
|
|
931
|
+
{
|
|
932
|
+
payer: payerSigner,
|
|
933
|
+
ata: recipientAta,
|
|
934
|
+
owner: recipient,
|
|
935
|
+
mint
|
|
936
|
+
},
|
|
937
|
+
{ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
|
|
938
|
+
)
|
|
939
|
+
);
|
|
940
|
+
let treasuryAta;
|
|
941
|
+
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
942
|
+
const treasuryOwner = address(paymentRequest.fee_address);
|
|
943
|
+
[treasuryAta] = await findAssociatedTokenPda({
|
|
944
|
+
owner: treasuryOwner,
|
|
945
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
946
|
+
mint
|
|
947
|
+
});
|
|
948
|
+
instructions.push(
|
|
949
|
+
getCreateAssociatedTokenIdempotentInstruction(
|
|
950
|
+
{
|
|
951
|
+
payer: payerSigner,
|
|
952
|
+
ata: treasuryAta,
|
|
953
|
+
owner: treasuryOwner,
|
|
954
|
+
mint
|
|
955
|
+
},
|
|
956
|
+
{ programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS }
|
|
957
|
+
)
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const providerTransferIx = getTransferCheckedInstruction({
|
|
961
|
+
source: payerAta,
|
|
962
|
+
mint,
|
|
963
|
+
destination: recipientAta,
|
|
964
|
+
authority: payerSigner,
|
|
965
|
+
amount: BigInt(providerAmount),
|
|
966
|
+
decimals: asset.decimals
|
|
718
967
|
});
|
|
719
968
|
const providerTransferIxWithReference = {
|
|
720
969
|
...providerTransferIx,
|
|
721
970
|
accounts: [...providerTransferIx.accounts, { address: reference, role: AccountRole.READONLY }]
|
|
722
971
|
};
|
|
723
|
-
|
|
724
|
-
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
972
|
+
instructions.push(providerTransferIxWithReference);
|
|
973
|
+
if (treasuryAta && paymentRequest.fee_address && feeAmount > 0) {
|
|
725
974
|
instructions.push(
|
|
726
|
-
|
|
727
|
-
source:
|
|
728
|
-
|
|
729
|
-
|
|
975
|
+
getTransferCheckedInstruction({
|
|
976
|
+
source: payerAta,
|
|
977
|
+
mint,
|
|
978
|
+
destination: treasuryAta,
|
|
979
|
+
authority: payerSigner,
|
|
980
|
+
amount: BigInt(feeAmount),
|
|
981
|
+
decimals: asset.decimals
|
|
730
982
|
})
|
|
731
983
|
);
|
|
732
984
|
}
|
|
@@ -2335,96 +2587,124 @@ var ElisymClient = class {
|
|
|
2335
2587
|
this.pool.close();
|
|
2336
2588
|
}
|
|
2337
2589
|
};
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
var
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
)
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
}
|
|
2380
|
-
const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
|
|
2381
|
-
if (fracPart.length > asset.decimals) {
|
|
2382
|
-
throw new Error(
|
|
2383
|
-
`${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
|
|
2384
|
-
);
|
|
2385
|
-
}
|
|
2386
|
-
const unit = 10n ** BigInt(asset.decimals);
|
|
2387
|
-
const whole = BigInt(wholePart);
|
|
2388
|
-
const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
|
|
2389
|
-
const raw = whole * unit + frac;
|
|
2390
|
-
if (raw === 0n) {
|
|
2391
|
-
throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
|
|
2392
|
-
}
|
|
2393
|
-
if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
2394
|
-
throw new Error(
|
|
2395
|
-
`${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
|
|
2396
|
-
);
|
|
2590
|
+
var DEFAULT_COMPUTE_UNIT_LIMIT2 = 2e5;
|
|
2591
|
+
var DEFAULT_PRIORITY_FEE_PERCENTILE2 = 75;
|
|
2592
|
+
var BASE_FEE_LAMPORTS_PER_SIGNATURE = 5000n;
|
|
2593
|
+
var FALLBACK_ATA_RENT_LAMPORTS = 2039280n;
|
|
2594
|
+
var SPL_TOKEN_ACCOUNT_SIZE = 165;
|
|
2595
|
+
async function estimateSolFeeLamports(rpc, paymentRequest, _payerAddress, options) {
|
|
2596
|
+
const numSignatures = options?.numSignatures ?? 1;
|
|
2597
|
+
const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT2;
|
|
2598
|
+
const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
|
|
2599
|
+
percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE2
|
|
2600
|
+
});
|
|
2601
|
+
const baseFeeLamports = BASE_FEE_LAMPORTS_PER_SIGNATURE * BigInt(numSignatures);
|
|
2602
|
+
const priorityFeeLamports = ceilDiv(
|
|
2603
|
+
priorityFeeMicroLamports * BigInt(computeUnitLimit),
|
|
2604
|
+
1000000n
|
|
2605
|
+
);
|
|
2606
|
+
const asset = resolveAssetFromPaymentRequest(paymentRequest);
|
|
2607
|
+
let rentLamports = 0n;
|
|
2608
|
+
let rentPerAtaLamports = 0n;
|
|
2609
|
+
let missingAtaCount = 0;
|
|
2610
|
+
if (asset.mint) {
|
|
2611
|
+
rentPerAtaLamports = await fetchAtaRent(rpc);
|
|
2612
|
+
const mint = address(asset.mint);
|
|
2613
|
+
const ataAccountsToCheck = [];
|
|
2614
|
+
const [recipientAta] = await findAssociatedTokenPda({
|
|
2615
|
+
owner: address(paymentRequest.recipient),
|
|
2616
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
2617
|
+
mint
|
|
2618
|
+
});
|
|
2619
|
+
ataAccountsToCheck.push(recipientAta);
|
|
2620
|
+
const feeAmount = paymentRequest.fee_amount ?? 0;
|
|
2621
|
+
if (paymentRequest.fee_address && feeAmount > 0) {
|
|
2622
|
+
const [treasuryAta] = await findAssociatedTokenPda({
|
|
2623
|
+
owner: address(paymentRequest.fee_address),
|
|
2624
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
2625
|
+
mint
|
|
2626
|
+
});
|
|
2627
|
+
ataAccountsToCheck.push(treasuryAta);
|
|
2628
|
+
}
|
|
2629
|
+
missingAtaCount = await countMissingAccounts(rpc, ataAccountsToCheck);
|
|
2630
|
+
rentLamports = rentPerAtaLamports * BigInt(missingAtaCount);
|
|
2397
2631
|
}
|
|
2398
|
-
|
|
2632
|
+
const totalLamports = baseFeeLamports + priorityFeeLamports + rentLamports;
|
|
2633
|
+
return {
|
|
2634
|
+
baseFeeLamports,
|
|
2635
|
+
priorityFeeLamports,
|
|
2636
|
+
rentLamports,
|
|
2637
|
+
totalLamports,
|
|
2638
|
+
breakdown: {
|
|
2639
|
+
numSignatures,
|
|
2640
|
+
priorityFeeMicroLamports,
|
|
2641
|
+
computeUnitLimit,
|
|
2642
|
+
rentPerAtaLamports,
|
|
2643
|
+
missingAtaCount
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2399
2646
|
}
|
|
2400
|
-
function
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2647
|
+
async function fetchAtaRent(rpc) {
|
|
2648
|
+
try {
|
|
2649
|
+
const lamports = await rpc.getMinimumBalanceForRentExemption(BigInt(SPL_TOKEN_ACCOUNT_SIZE)).send();
|
|
2650
|
+
if (typeof lamports === "bigint") {
|
|
2651
|
+
return lamports;
|
|
2652
|
+
}
|
|
2653
|
+
if (typeof lamports === "number" && Number.isFinite(lamports) && lamports > 0) {
|
|
2654
|
+
return BigInt(lamports);
|
|
2655
|
+
}
|
|
2656
|
+
return FALLBACK_ATA_RENT_LAMPORTS;
|
|
2657
|
+
} catch {
|
|
2658
|
+
return FALLBACK_ATA_RENT_LAMPORTS;
|
|
2408
2659
|
}
|
|
2409
|
-
return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
|
|
2410
2660
|
}
|
|
2411
|
-
async function
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
} catch (e) {
|
|
2661
|
+
async function countMissingAccounts(rpc, accounts) {
|
|
2662
|
+
if (accounts.length === 0) {
|
|
2663
|
+
return 0;
|
|
2664
|
+
}
|
|
2665
|
+
let missing = 0;
|
|
2666
|
+
for (const acct of accounts) {
|
|
2418
2667
|
try {
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2668
|
+
const res = await rpc.getAccountInfo(acct, { encoding: "base64" }).send();
|
|
2669
|
+
if (!res || !res.value) {
|
|
2670
|
+
missing++;
|
|
2671
|
+
}
|
|
2421
2672
|
} catch {
|
|
2673
|
+
missing++;
|
|
2422
2674
|
}
|
|
2423
|
-
throw e;
|
|
2424
2675
|
}
|
|
2676
|
+
return missing;
|
|
2677
|
+
}
|
|
2678
|
+
function ceilDiv(num, denom) {
|
|
2679
|
+
if (denom === 0n) {
|
|
2680
|
+
throw new Error("division by zero in ceilDiv");
|
|
2681
|
+
}
|
|
2682
|
+
const q = num / denom;
|
|
2683
|
+
const r = num % denom;
|
|
2684
|
+
return r === 0n ? q : q + 1n;
|
|
2685
|
+
}
|
|
2686
|
+
function formatFeeBreakdown(estimate) {
|
|
2687
|
+
const line = (label, lamports) => {
|
|
2688
|
+
const label16 = label.padEnd(14);
|
|
2689
|
+
return ` ${label16}${lamports.toString()} lamports (${lamportsToSol(lamports)} SOL)`;
|
|
2690
|
+
};
|
|
2691
|
+
const lines = [
|
|
2692
|
+
"Estimated SOL cost for this transaction:",
|
|
2693
|
+
line("Base fee:", estimate.baseFeeLamports),
|
|
2694
|
+
line("Priority fee:", estimate.priorityFeeLamports)
|
|
2695
|
+
];
|
|
2696
|
+
if (estimate.rentLamports > 0n) {
|
|
2697
|
+
lines.push(line("ATA rent:", estimate.rentLamports));
|
|
2698
|
+
}
|
|
2699
|
+
lines.push(line("Total:", estimate.totalLamports));
|
|
2700
|
+
return lines.join("\n");
|
|
2701
|
+
}
|
|
2702
|
+
function lamportsToSol(lamports) {
|
|
2703
|
+
const LAMPORTS_PER_SOL2 = 1000000000n;
|
|
2704
|
+
const whole = lamports / LAMPORTS_PER_SOL2;
|
|
2705
|
+
const frac = lamports % LAMPORTS_PER_SOL2;
|
|
2706
|
+
return `${whole}.${frac.toString().padStart(9, "0")}`;
|
|
2425
2707
|
}
|
|
2426
|
-
|
|
2427
|
-
// src/config/global.ts
|
|
2428
2708
|
var SessionSpendLimitEntrySchema = z.object({
|
|
2429
2709
|
chain: z.enum(["solana"]),
|
|
2430
2710
|
token: z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
|
|
@@ -2434,30 +2714,6 @@ var SessionSpendLimitEntrySchema = z.object({
|
|
|
2434
2714
|
var GlobalConfigSchema = z.object({
|
|
2435
2715
|
session_spend_limits: z.array(SessionSpendLimitEntrySchema).max(16).optional()
|
|
2436
2716
|
}).strict();
|
|
2437
|
-
function isEnoent(e) {
|
|
2438
|
-
return typeof e === "object" && e !== null && "code" in e && e.code === "ENOENT";
|
|
2439
|
-
}
|
|
2440
|
-
async function loadGlobalConfig(path) {
|
|
2441
|
-
let raw;
|
|
2442
|
-
try {
|
|
2443
|
-
raw = await readFile(path, "utf-8");
|
|
2444
|
-
} catch (e) {
|
|
2445
|
-
if (isEnoent(e)) {
|
|
2446
|
-
return {};
|
|
2447
|
-
}
|
|
2448
|
-
throw e;
|
|
2449
|
-
}
|
|
2450
|
-
if (raw.trim() === "") {
|
|
2451
|
-
return {};
|
|
2452
|
-
}
|
|
2453
|
-
const parsed = YAML2.parse(raw);
|
|
2454
|
-
return GlobalConfigSchema.parse(parsed ?? {});
|
|
2455
|
-
}
|
|
2456
|
-
async function writeGlobalConfig(path, config) {
|
|
2457
|
-
const validated = GlobalConfigSchema.parse(config);
|
|
2458
|
-
const body = YAML2.stringify(validated);
|
|
2459
|
-
await writeFileAtomic(path, body, 420);
|
|
2460
|
-
}
|
|
2461
2717
|
function formatSol(lamports) {
|
|
2462
2718
|
const sol = new Decimal2(lamports).div(LAMPORTS_PER_SOL);
|
|
2463
2719
|
if (sol.gte(1e6)) {
|
|
@@ -2649,6 +2905,6 @@ function makeCensor() {
|
|
|
2649
2905
|
};
|
|
2650
2906
|
}
|
|
2651
2907
|
|
|
2652
|
-
export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, formatAssetAmount, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind,
|
|
2908
|
+
export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
|
|
2653
2909
|
//# sourceMappingURL=index.js.map
|
|
2654
2910
|
//# sourceMappingURL=index.js.map
|