@secondlayer/sdk 6.19.0 → 6.21.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/dist/index.js CHANGED
@@ -281,6 +281,19 @@ class Subgraphs extends BaseClient {
281
281
  const qs = options?.force ? "?force=true" : "";
282
282
  return this.request("DELETE", `/api/subgraphs/${name}${qs}`);
283
283
  }
284
+ async publish(name) {
285
+ return this.request("POST", `/api/subgraphs/${name}/publish`);
286
+ }
287
+ async unpublish(name) {
288
+ return this.request("POST", `/api/subgraphs/${name}/unpublish`);
289
+ }
290
+ async rows(name, table, params = {}) {
291
+ const { cursor, ...rest } = params;
292
+ const qs = buildSubgraphQueryString(rest);
293
+ const sep = qs ? "&" : "?";
294
+ const cursorQs = cursor ? `${sep}cursor=${encodeURIComponent(cursor)}` : "";
295
+ return this.request("GET", `/v1/subgraphs/${name}/${table}${qs}${cursorQs}`);
296
+ }
284
297
  async operations(name) {
285
298
  return this.request("GET", `/api/subgraphs/${name}/operations`);
286
299
  }
@@ -429,164 +442,6 @@ class Contracts extends BaseClient {
429
442
  }
430
443
  }
431
444
 
432
- // src/datasets/client.ts
433
- var PARAM_KEYS = {
434
- fromBlock: "from_block",
435
- toBlock: "to_block",
436
- functionName: "function_name",
437
- delegateTo: "delegate_to",
438
- signerKey: "signer_key",
439
- rewardCycle: "reward_cycle",
440
- eventType: "event_type",
441
- bnsId: "bns_id"
442
- };
443
- var CURSOR_SLUGS = {
444
- "stx-transfers": { path: "stx-transfers", rowKey: "events" },
445
- "sbtc-events": { path: "sbtc/events", rowKey: "events" },
446
- "sbtc-token-events": { path: "sbtc/token-events", rowKey: "events" },
447
- "pox-4-calls": { path: "pox-4/calls", rowKey: "calls" },
448
- "burnchain-rewards": { path: "burnchain/rewards", rowKey: "rewards" },
449
- "burnchain-reward-slots": {
450
- path: "burnchain/reward-slots",
451
- rowKey: "slots"
452
- },
453
- "bns-events": { path: "bns/events", rowKey: "events" },
454
- "bns-namespace-events": { path: "bns/namespace-events", rowKey: "events" },
455
- "bns-marketplace-events": {
456
- path: "bns/marketplace-events",
457
- rowKey: "events"
458
- }
459
- };
460
- function catalogPathTail(path) {
461
- return path.replace(/^\/?v1\/datasets\//, "").replace(/^\/+/, "");
462
- }
463
-
464
- class Datasets extends BaseClient {
465
- catalogPromise;
466
- constructor(options = {}) {
467
- super(options);
468
- }
469
- listDatasets() {
470
- return this.request("GET", "/v1/datasets");
471
- }
472
- async get(slug, params = {}) {
473
- const { path, rowKey } = await this.resolveDataset(slug);
474
- const env = await this.requestPath(path, this.paramsToQuery(params));
475
- const value = env[rowKey];
476
- const rows = Array.isArray(value) ? value : value == null ? [] : [value];
477
- return {
478
- rows,
479
- next_cursor: env.next_cursor ?? null,
480
- tip: env.tip
481
- };
482
- }
483
- async query(slug, params = {}) {
484
- const d = CURSOR_SLUGS[slug];
485
- if (!d) {
486
- throw new Error(`unknown cursor dataset "${slug}" (use one of: ${Object.keys(CURSOR_SLUGS).join(", ")})`);
487
- }
488
- const env = await this.requestPath(d.path, this.paramsToQuery(params));
489
- return {
490
- rows: env[d.rowKey] ?? [],
491
- next_cursor: env.next_cursor ?? null,
492
- tip: env.tip
493
- };
494
- }
495
- stxTransfers = this.cursorDataset("stx-transfers", "events");
496
- sbtcEvents = this.cursorDataset("sbtc/events", "events");
497
- sbtcTokenEvents = this.cursorDataset("sbtc/token-events", "events");
498
- pox4Calls = this.cursorDataset("pox-4/calls", "calls");
499
- burnchainRewards = this.cursorDataset("burnchain/rewards", "rewards");
500
- burnchainRewardSlots = this.cursorDataset("burnchain/reward-slots", "slots");
501
- bnsEvents = this.cursorDataset("bns/events", "events");
502
- bnsNamespaceEvents = this.cursorDataset("bns/namespace-events", "events");
503
- bnsMarketplaceEvents = this.cursorDataset("bns/marketplace-events", "events");
504
- bnsNames(params = {}) {
505
- return this.requestPath("bns/names", buildQuery({
506
- namespace: params.namespace,
507
- owner: params.owner,
508
- limit: params.limit,
509
- offset: params.offset
510
- }));
511
- }
512
- bnsNamespaces() {
513
- return this.requestPath("bns/namespaces", "");
514
- }
515
- bnsResolve(fqn) {
516
- return this.requestPath("bns/resolve", buildQuery({ fqn }));
517
- }
518
- networkHealth() {
519
- return this.requestPath("network-health/summary", "");
520
- }
521
- requestPath(path, query) {
522
- return this.request("GET", `/v1/datasets/${path}${query}`);
523
- }
524
- async resolveDataset(slug) {
525
- const cursor = CURSOR_SLUGS[slug];
526
- if (cursor)
527
- return { path: cursor.path, rowKey: cursor.rowKey };
528
- const families = await this.loadCatalog();
529
- const tail = catalogPathTail(slug);
530
- const match = families.find((f) => f.family === slug || catalogPathTail(f.path) === tail);
531
- if (!match) {
532
- throw new Error(`unknown dataset "${slug}" (available: ${families.map((f) => f.family).join(", ")})`);
533
- }
534
- return { path: catalogPathTail(match.path), rowKey: match.row_key };
535
- }
536
- loadCatalog() {
537
- this.catalogPromise ??= (async () => {
538
- const raw = await this.listDatasets();
539
- const families = raw.families;
540
- if (!Array.isArray(families)) {
541
- throw new Error("dataset catalog response missing families[]");
542
- }
543
- return families;
544
- })();
545
- return this.catalogPromise;
546
- }
547
- paramsToQuery(params) {
548
- const mapped = {};
549
- for (const [k, v] of Object.entries(params)) {
550
- if (v === undefined || v === null || k === "batchSize" || k === "signal")
551
- continue;
552
- mapped[PARAM_KEYS[k] ?? k] = v;
553
- }
554
- return buildQuery(mapped);
555
- }
556
- cursorDataset(path, rowKey) {
557
- const list = async (params = {}) => {
558
- const envelope = await this.requestPath(path, this.paramsToQuery(params));
559
- return {
560
- rows: envelope[rowKey] ?? [],
561
- next_cursor: envelope.next_cursor ?? null,
562
- tip: envelope.tip
563
- };
564
- };
565
- const walk = async function* (params = {}) {
566
- const batchSize = params.batchSize ?? 200;
567
- let cursor = params.cursor ?? null;
568
- let first = true;
569
- while (!params.signal?.aborted) {
570
- const env = await list({
571
- ...params,
572
- limit: batchSize,
573
- cursor: first ? params.cursor : cursor ?? undefined
574
- });
575
- for (const row of env.rows) {
576
- if (params.signal?.aborted)
577
- return;
578
- yield row;
579
- }
580
- if (!env.next_cursor || env.next_cursor === cursor || env.rows.length < batchSize)
581
- return;
582
- cursor = env.next_cursor;
583
- first = false;
584
- }
585
- }.bind(this);
586
- return { list, walk };
587
- }
588
- }
589
-
590
445
  // src/index-api/client.ts
591
446
  function firstWalkFromHeight(params) {
592
447
  if (params.fromHeight !== undefined)
@@ -606,18 +461,18 @@ class Index extends BaseClient {
606
461
  discover() {
607
462
  return this.request("GET", "/v1/index");
608
463
  }
609
- ftTransfers = {
464
+ ftTransfers = Object.assign((params = {}) => this.listFtTransfers(params), {
610
465
  list: (params = {}) => this.listFtTransfers(params),
611
466
  walk: (params = {}) => this.walkFtTransfers(params)
612
- };
613
- nftTransfers = {
467
+ });
468
+ nftTransfers = Object.assign((params = {}) => this.listNftTransfers(params), {
614
469
  list: (params = {}) => this.listNftTransfers(params),
615
470
  walk: (params = {}) => this.walkNftTransfers(params)
616
- };
617
- events = {
471
+ });
472
+ events = Object.assign((params) => this.listEvents(params), {
618
473
  list: (params) => this.listEvents(params),
619
474
  walk: (params) => this.walkEvents(params)
620
- };
475
+ });
621
476
  contractCalls = {
622
477
  list: (params = {}) => this.listContractCalls(params),
623
478
  walk: (params = {}) => this.walkContractCalls(params)
@@ -1182,6 +1037,38 @@ async function consumeStreamsEvents(opts) {
1182
1037
  }
1183
1038
  return { cursor, pages, emptyPolls };
1184
1039
  }
1040
+ async function* iterateStreamsBatches(opts) {
1041
+ const sleep = opts.sleep ?? defaultSleep;
1042
+ let cursor = opts.fromCursor ?? null;
1043
+ while (!opts.signal?.aborted) {
1044
+ const envelope = await opts.fetchEvents({
1045
+ cursor,
1046
+ limit: opts.batchSize,
1047
+ types: opts.types,
1048
+ notTypes: opts.notTypes,
1049
+ contractId: opts.contractId,
1050
+ sender: opts.sender,
1051
+ recipient: opts.recipient,
1052
+ assetIdentifier: opts.assetIdentifier
1053
+ });
1054
+ const checkpoint = envelope.next_cursor ?? cursor;
1055
+ if (envelope.events.length > 0 || envelope.reorgs.length > 0) {
1056
+ yield {
1057
+ events: envelope.events,
1058
+ cursor: checkpoint,
1059
+ tip: envelope.tip,
1060
+ reorgs: envelope.reorgs
1061
+ };
1062
+ }
1063
+ const advanced = checkpoint !== null && checkpoint !== cursor;
1064
+ cursor = checkpoint;
1065
+ if (!advanced && envelope.events.length === 0) {
1066
+ if (opts.signal?.aborted)
1067
+ return;
1068
+ await sleep(opts.intervalMs, opts.signal);
1069
+ }
1070
+ }
1071
+ }
1185
1072
  async function* streamStreamsEvents(opts) {
1186
1073
  const sleep = opts.sleep ?? defaultSleep;
1187
1074
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
@@ -1559,6 +1446,21 @@ function createStreamsClient(options) {
1559
1446
  })}`);
1560
1447
  }
1561
1448
  return {
1449
+ consume(params = {}) {
1450
+ return iterateStreamsBatches({
1451
+ fromCursor: params.cursor,
1452
+ batchSize: params.batchSize ?? 100,
1453
+ intervalMs: params.intervalMs ?? 2000,
1454
+ types: params.types,
1455
+ notTypes: params.notTypes,
1456
+ contractId: params.contractId,
1457
+ sender: params.sender,
1458
+ recipient: params.recipient,
1459
+ assetIdentifier: params.assetIdentifier,
1460
+ signal: params.signal,
1461
+ fetchEvents
1462
+ });
1463
+ },
1562
1464
  events: {
1563
1465
  list: listEvents,
1564
1466
  byTxId(txId) {
@@ -1711,7 +1613,6 @@ class Subscriptions extends BaseClient {
1711
1613
  class SecondLayer extends BaseClient {
1712
1614
  streams;
1713
1615
  index;
1714
- datasets;
1715
1616
  contracts;
1716
1617
  subgraphs;
1717
1618
  subscriptions;
@@ -1726,13 +1627,15 @@ class SecondLayer extends BaseClient {
1726
1627
  dumpsBaseUrl: options.dumpsBaseUrl
1727
1628
  });
1728
1629
  this.index = new Index(options);
1729
- this.datasets = new Datasets(options);
1730
1630
  this.contracts = new Contracts(options);
1731
1631
  this.subgraphs = new Subgraphs(options);
1732
1632
  this.subscriptions = new Subscriptions(options);
1733
1633
  this.apiKeys = new ApiKeys(options);
1734
1634
  this.projects = new Projects(options);
1735
1635
  }
1636
+ async batch(requests) {
1637
+ return this.request("POST", "/v1/batch", { requests });
1638
+ }
1736
1639
  async context() {
1737
1640
  const safe = (p) => p.then((v) => v).catch(() => null);
1738
1641
  const [
@@ -2093,6 +1996,220 @@ function decodePrint(event) {
2093
1996
  import {
2094
1997
  STREAMS_EVENT_TYPES
2095
1998
  } from "@secondlayer/shared";
1999
+ // src/x402.ts
2000
+ import {
2001
+ X402_TOKENS,
2002
+ findX402TokenByAsset
2003
+ } from "@secondlayer/shared/x402";
2004
+ import {
2005
+ serializeTransactionHex,
2006
+ signTransactionWithAccount
2007
+ } from "@secondlayer/stacks/transactions";
2008
+ import { buildExactTransfer } from "@secondlayer/stacks/x402";
2009
+ var DEFAULT_PREFER_ASSETS = [
2010
+ "sBTC",
2011
+ "USDCx",
2012
+ "STX"
2013
+ ];
2014
+ var DEFAULT_NONCE_NODE_URL = "https://api.hiro.so";
2015
+
2016
+ class X402SpendGuardError extends Error {
2017
+ constructor(message) {
2018
+ super(message);
2019
+ this.name = "X402SpendGuardError";
2020
+ }
2021
+ }
2022
+ function b64encode(value) {
2023
+ return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
2024
+ }
2025
+ function b64decodeJson(value) {
2026
+ try {
2027
+ return JSON.parse(Buffer.from(value, "base64").toString("utf8"));
2028
+ } catch {
2029
+ return null;
2030
+ }
2031
+ }
2032
+ async function readX402Challenge(res) {
2033
+ const header = res.headers.get("PAYMENT-REQUIRED");
2034
+ if (header) {
2035
+ const decoded = b64decodeJson(header);
2036
+ if (decoded)
2037
+ return decoded;
2038
+ }
2039
+ try {
2040
+ return await res.clone().json();
2041
+ } catch {
2042
+ return null;
2043
+ }
2044
+ }
2045
+ function readX402Receipt(res) {
2046
+ const header = res.headers.get("PAYMENT-RESPONSE");
2047
+ return header ? b64decodeJson(header) : null;
2048
+ }
2049
+ function selectOffer(challenge, opts = {}) {
2050
+ const prefer = opts.preferAssets ?? DEFAULT_PREFER_ASSETS;
2051
+ for (const symbol of prefer) {
2052
+ const token = X402_TOKENS[symbol];
2053
+ const accept = challenge.accepts.find((a) => a.asset === token.asset);
2054
+ if (!accept)
2055
+ continue;
2056
+ const cap = opts.maxAmountPerCall?.[symbol];
2057
+ if (cap !== undefined && BigInt(accept.amount) > cap)
2058
+ continue;
2059
+ return { accept, symbol };
2060
+ }
2061
+ throw new X402SpendGuardError("no x402 offer matched preferAssets within maxAmountPerCall");
2062
+ }
2063
+ async function resolveAccountNonce(address, nodeUrl = DEFAULT_NONCE_NODE_URL) {
2064
+ const res = await fetch(`${nodeUrl.replace(/\/$/, "")}/v2/accounts/${address}?proof=0`);
2065
+ if (!res.ok)
2066
+ throw new Error(`x402 nonce lookup failed: ${res.status}`);
2067
+ const json = await res.json();
2068
+ return json.nonce;
2069
+ }
2070
+ async function buildSignedX402Payment(opts) {
2071
+ const accept = opts.asset ? opts.challenge.accepts.find((a) => a.asset === opts.asset) : opts.challenge.accepts[0];
2072
+ if (!accept) {
2073
+ throw new Error(`No x402 offer${opts.asset ? ` for asset ${opts.asset}` : ""}`);
2074
+ }
2075
+ const token = findX402TokenByAsset(accept.asset);
2076
+ if (!token)
2077
+ throw new Error(`Unknown x402 asset: ${accept.asset}`);
2078
+ const asset = token.contractId && token.assetName ? {
2079
+ kind: "sip010",
2080
+ contractId: token.contractId,
2081
+ assetName: token.assetName
2082
+ } : { kind: "stx" };
2083
+ const tx = buildExactTransfer({
2084
+ asset,
2085
+ amount: BigInt(accept.amount),
2086
+ payTo: accept.payTo,
2087
+ payer: opts.account.address,
2088
+ payerPublicKey: opts.account.publicKey,
2089
+ accountNonce: opts.accountNonce,
2090
+ nonce: accept.extra.nonce,
2091
+ chain: opts.chain
2092
+ });
2093
+ const signed = await signTransactionWithAccount(tx, opts.account);
2094
+ const header = b64encode({
2095
+ x402Version: opts.challenge.x402Version ?? 2,
2096
+ scheme: "exact",
2097
+ network: accept.network,
2098
+ asset: accept.asset,
2099
+ payload: { transaction: serializeTransactionHex(signed) },
2100
+ extra: { nonce: accept.extra.nonce }
2101
+ });
2102
+ return { header, accept };
2103
+ }
2104
+ function withX402(baseFetch, opts) {
2105
+ const sessions = new Map;
2106
+ let balanceToken = opts.balanceToken ?? null;
2107
+ let toppingUp = false;
2108
+ const originOf = (input) => {
2109
+ try {
2110
+ return new URL(String(input)).origin;
2111
+ } catch {
2112
+ return "";
2113
+ }
2114
+ };
2115
+ const wrapped = async (input, init) => {
2116
+ const origin = originOf(input);
2117
+ const run = (extra, signal2) => baseFetch(input, {
2118
+ ...init,
2119
+ headers: { ...init?.headers, ...extra },
2120
+ ...signal2 ? { signal: signal2 } : {}
2121
+ });
2122
+ const maybeTopUp = (res) => {
2123
+ const policy = opts.topUp;
2124
+ if (!policy || toppingUp || !origin)
2125
+ return;
2126
+ const remaining = res.headers.get("X-BALANCE-REMAINING-USD");
2127
+ if (remaining === null || Number(remaining) >= policy.whenBelow)
2128
+ return;
2129
+ toppingUp = true;
2130
+ (async () => {
2131
+ try {
2132
+ const dep = await wrapped(`${origin}/v1/x402/deposit?usd=${policy.usd}`, { method: "POST" });
2133
+ if (dep.ok) {
2134
+ const body = await dep.json();
2135
+ if (body.balance_token)
2136
+ balanceToken = body.balance_token;
2137
+ }
2138
+ } catch {} finally {
2139
+ toppingUp = false;
2140
+ }
2141
+ })();
2142
+ };
2143
+ const remember = (res) => {
2144
+ const voucher = res.headers.get("PAYMENT-SESSION");
2145
+ if (voucher && origin)
2146
+ sessions.set(origin, voucher);
2147
+ maybeTopUp(res);
2148
+ return res;
2149
+ };
2150
+ const cached = origin ? sessions.get(origin) : undefined;
2151
+ const first = await run({
2152
+ ...cached ? { "PAYMENT-SESSION": cached } : {},
2153
+ ...balanceToken ? { "PAYMENT-BALANCE": balanceToken } : {}
2154
+ });
2155
+ if (first.status !== 402)
2156
+ return remember(first);
2157
+ if (cached && origin)
2158
+ sessions.delete(origin);
2159
+ const challenge = await readX402Challenge(first);
2160
+ if (!challenge)
2161
+ return first;
2162
+ const { accept } = selectOffer(challenge, opts);
2163
+ const accountNonce = opts.accountNonce ?? await resolveAccountNonce(opts.account.address, opts.nodeUrl);
2164
+ const { header } = await buildSignedX402Payment({
2165
+ challenge,
2166
+ account: opts.account,
2167
+ accountNonce,
2168
+ asset: accept.asset,
2169
+ chain: opts.chain
2170
+ });
2171
+ opts.onSettling?.({ asset: accept.asset, amount: accept.amount });
2172
+ const signal = opts.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
2173
+ return remember(await run({ "PAYMENT-SIGNATURE": header }, signal));
2174
+ };
2175
+ return wrapped;
2176
+ }
2177
+ function createX402Client(opts) {
2178
+ const f = withX402(opts.fetch ?? fetch, opts);
2179
+ const base = opts.baseUrl.replace(/\/$/, "");
2180
+ async function request(method, path, o = {}) {
2181
+ const qs = o.query ? `?${new URLSearchParams(o.query).toString()}` : "";
2182
+ const init = { method };
2183
+ if (o.body !== undefined) {
2184
+ init.body = JSON.stringify(o.body);
2185
+ init.headers = { "content-type": "application/json" };
2186
+ }
2187
+ const response = await f(`${base}${path}${qs}`, init);
2188
+ const data = await response.json().catch(() => null);
2189
+ return { data, payment: readX402Receipt(response), response };
2190
+ }
2191
+ return {
2192
+ get: (path, o) => request("GET", path, o),
2193
+ post: (path, o) => request("POST", path, o)
2194
+ };
2195
+ }
2196
+ async function payAndRetry(doFetch, opts) {
2197
+ const first = await doFetch({});
2198
+ if (first.status !== 402)
2199
+ return first;
2200
+ const challenge = await readX402Challenge(first);
2201
+ if (!challenge)
2202
+ return first;
2203
+ const { accept } = selectOffer(challenge, opts);
2204
+ const { header } = await buildSignedX402Payment({
2205
+ challenge,
2206
+ account: opts.account,
2207
+ accountNonce: opts.accountNonce,
2208
+ asset: accept.asset,
2209
+ chain: opts.chain
2210
+ });
2211
+ return doFetch({ "PAYMENT-SIGNATURE": header });
2212
+ }
2096
2213
  // src/webhooks.ts
2097
2214
  import { verifySecondlayerSignatureValues } from "@secondlayer/shared/crypto/secondlayer-webhook";
2098
2215
  import {
@@ -2218,11 +2335,17 @@ function verifyTransactionProof(proof, opts) {
2218
2335
  };
2219
2336
  }
2220
2337
  export {
2338
+ withX402,
2221
2339
  verifyWebhookSignature,
2222
2340
  verifyTransactionProof,
2223
2341
  verifySecondlayerSignature,
2224
2342
  trigger,
2225
2343
  toJsonSafe,
2344
+ selectOffer,
2345
+ resolveAccountNonce,
2346
+ readX402Receipt,
2347
+ readX402Challenge,
2348
+ payAndRetry,
2226
2349
  isStxTransfer,
2227
2350
  isStxMint,
2228
2351
  isStxLock,
@@ -2248,7 +2371,10 @@ export {
2248
2371
  decodeFtMint,
2249
2372
  decodeFtBurn,
2250
2373
  decodeClarityValue,
2374
+ createX402Client,
2251
2375
  createStreamsClient,
2376
+ buildSignedX402Payment,
2377
+ X402SpendGuardError,
2252
2378
  VersionConflictError,
2253
2379
  ValidationError,
2254
2380
  Subscriptions,
@@ -2259,15 +2385,14 @@ export {
2259
2385
  RateLimitError,
2260
2386
  Projects,
2261
2387
  Index,
2262
- Datasets,
2388
+ DEFAULT_PREFER_ASSETS,
2263
2389
  Cursor,
2264
2390
  Contracts,
2265
- CURSOR_SLUGS,
2266
2391
  ByoBreakingChangeError,
2267
2392
  AuthError,
2268
2393
  ApiKeys,
2269
2394
  ApiError
2270
2395
  };
2271
2396
 
2272
- //# debugId=AD881ECDD1B046B864756E2164756E21
2397
+ //# debugId=EAC231197163FBBB64756E2164756E21
2273
2398
  //# sourceMappingURL=index.js.map