@ardrive/turbo-sdk 1.43.0 → 1.44.0-alpha.1

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.
@@ -41,13 +41,14 @@ const chunkingHeader = { 'x-chunking-version': '2' };
41
41
  * uploading them in parallel, and emitting progress/error events.
42
42
  */
43
43
  class ChunkedUploader {
44
- constructor({ http, token, maxChunkConcurrency = exports.defaultMaxChunkConcurrency, maxFinalizeMs, chunkByteCount = exports.defaultChunkByteCount, logger = logger_js_1.Logger.default, chunkingMode = 'auto', dataItemByteCount, x402, x402RefundIdentity, }) {
44
+ constructor({ http, token, maxChunkConcurrency = exports.defaultMaxChunkConcurrency, maxFinalizeMs, chunkByteCount = exports.defaultChunkByteCount, logger = logger_js_1.Logger.default, chunkingMode = 'auto', dataItemByteCount, x402, x402RefundIdentity, paidUploadId, }) {
45
45
  this.assertChunkParams({
46
46
  chunkByteCount,
47
47
  chunkingMode,
48
48
  maxChunkConcurrency,
49
49
  maxFinalizeMs,
50
50
  });
51
+ this.paidUploadId = paidUploadId;
51
52
  this.chunkByteCount = chunkByteCount;
52
53
  this.maxChunkConcurrency = maxChunkConcurrency;
53
54
  this.maxFinalizeMs = maxFinalizeMs;
@@ -145,6 +146,14 @@ class ChunkedUploader {
145
146
  this.paidUploadId = res.id;
146
147
  return res.id;
147
148
  }
149
+ /**
150
+ * The paid upload id, once one has been bought. `uploadFile` reads this after
151
+ * a failed attempt so the next attempt resumes the upload already paid for
152
+ * rather than buying a second one.
153
+ */
154
+ get currentPaidUploadId() {
155
+ return this.paidUploadId;
156
+ }
148
157
  async initUpload() {
149
158
  if (this.x402 !== undefined) {
150
159
  return this.initPaidUpload(this.x402);
@@ -52,8 +52,21 @@ class TurboHTTPService {
52
52
  this.baseURL = url;
53
53
  this.retryConfig = retryConfig;
54
54
  }
55
+ /**
56
+ * Refuse to pay over cleartext.
57
+ *
58
+ * An x402 payment authorization is a bearer credential: anyone who observes
59
+ * it can submit it. Sending one over `http:` hands it to the network, so a
60
+ * misconfigured service URL must fail loudly rather than quietly leak.
61
+ */
62
+ assertSecureForPayment() {
63
+ if (!this.baseURL.startsWith('https:') && !isLoopback(this.baseURL)) {
64
+ throw new Error(`Refusing to send an x402 payment over a non-HTTPS URL: ${this.baseURL}`);
65
+ }
66
+ }
55
67
  async get({ endpoint, signal, allowedStatuses = [200, 202], headers, x402Options, }) {
56
68
  if (x402Options !== undefined) {
69
+ this.assertSecureForPayment();
57
70
  const maxMUSDCAmount = x402Options.maxMUSDCAmount !== undefined
58
71
  ? BigInt(x402Options.maxMUSDCAmount.toString())
59
72
  : undefined;
@@ -162,6 +175,7 @@ class TurboHTTPService {
162
175
  endpoint,
163
176
  x402Options,
164
177
  });
178
+ this.assertSecureForPayment();
165
179
  const { body, duplex } = await toFetchBody(data);
166
180
  return this.tryRequest(async () => {
167
181
  const maxMUSDCAmount = x402Options.maxMUSDCAmount !== undefined
@@ -213,3 +227,13 @@ function isFirefoxOrSafari() {
213
227
  !ua.includes('Chrome') &&
214
228
  !ua.includes('Chromium')));
215
229
  }
230
+ /** Loopback is exempt: local development never leaves the machine. */
231
+ function isLoopback(url) {
232
+ try {
233
+ const { hostname } = new URL(url);
234
+ return (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1');
235
+ }
236
+ catch {
237
+ return false;
238
+ }
239
+ }
@@ -657,13 +657,20 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
657
657
  * The owner needs a Solana key to sign with, NOT a funded one — Turbo pays
658
658
  * every lamport of fee and rent.
659
659
  */
660
- async buyArNSName({ name, owner, type = 'lease', years, paidBy, onNonce, }) {
660
+ async buyArNSName({ name, owner, type = 'lease', years, paidBy, onNonce, antState, }) {
661
661
  return this.completeArNSAction('buy-name', {
662
662
  name,
663
663
  ownerAddress: await owner.getAddress(),
664
664
  type,
665
665
  ...(years !== undefined ? { years } : {}),
666
666
  ...(paidBy !== undefined ? { paidBy } : {}),
667
+ /*
668
+ Spread conditionally, like `paidBy` — an `antState: undefined` key
669
+ would serialize into the body and is not the same request as omitting
670
+ it. Sent whole in the BODY rather than the query string: it is an
671
+ object, and `createArNSAction` already posts every param as JSON.
672
+ */
673
+ ...(antState !== undefined ? { antState } : {}),
667
674
  }, owner, { onNonce });
668
675
  }
669
676
  /** Extend a lease. Permissionless on chain — no owner signature needed. */
@@ -193,6 +193,20 @@ class TurboUnauthenticatedClient {
193
193
  maxMUSDCAmount,
194
194
  });
195
195
  }
196
+ /**
197
+ * Price a signed data item for an x402 upload without sending it. See
198
+ * `TurboUnauthenticatedUploadService.getX402PriceForDataItem`.
199
+ */
200
+ getX402PriceForDataItem(p) {
201
+ return this.uploadService.getX402PriceForDataItem(p);
202
+ }
203
+ /**
204
+ * Price raw data for an x402 upload, including the data-item wrapping
205
+ * overhead. See `TurboUnauthenticatedUploadService.getX402PriceForRawData`.
206
+ */
207
+ getX402PriceForRawData(p) {
208
+ return this.uploadService.getX402PriceForRawData(p);
209
+ }
196
210
  }
197
211
  exports.TurboUnauthenticatedClient = TurboUnauthenticatedClient;
198
212
  class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
@@ -381,5 +395,19 @@ class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
381
395
  maxMUSDCAmount,
382
396
  });
383
397
  }
398
+ /**
399
+ * Price a signed data item for an x402 upload without sending it. See
400
+ * `TurboUnauthenticatedUploadService.getX402PriceForDataItem`.
401
+ */
402
+ getX402PriceForDataItem(p) {
403
+ return this.uploadService.getX402PriceForDataItem(p);
404
+ }
405
+ /**
406
+ * Price raw data for an x402 upload, including the data-item wrapping
407
+ * overhead. See `TurboUnauthenticatedUploadService.getX402PriceForRawData`.
408
+ */
409
+ getX402PriceForRawData(p) {
410
+ return this.uploadService.getX402PriceForRawData(p);
411
+ }
384
412
  }
385
413
  exports.TurboAuthenticatedClient = TurboAuthenticatedClient;
@@ -17,6 +17,7 @@ exports.TurboAuthenticatedBaseUploadService = exports.TurboUnauthenticatedUpload
17
17
  * limitations under the License.
18
18
  */
19
19
  const bignumber_js_1 = require("bignumber.js");
20
+ const node_stream_1 = require("node:stream");
20
21
  const plimit_lit_1 = require("plimit-lit");
21
22
  const types_js_1 = require("../types.js");
22
23
  const common_js_1 = require("../utils/common.js");
@@ -80,6 +81,29 @@ class TurboUnauthenticatedUploadService {
80
81
  headers['x-paid-by'] = paidBy;
81
82
  }
82
83
  }
84
+ if (x402Options !== undefined) {
85
+ /*
86
+ x402 needs a body fetch can measure, and one it can send twice.
87
+
88
+ `content-length` is a forbidden header, so setting it above does
89
+ nothing: a streamed body goes out chunked with no length. The service
90
+ prices from that header, so without it there is no 402 challenge — and
91
+ the protocol's paid retry has a spent stream to replay, which fails
92
+ outright. Buffering fixes both: the length is declared honestly and the
93
+ body can be re-sent.
94
+
95
+ Bounded by the chunking decision above — anything over two chunks took
96
+ the chunked path, which pays at create and never reaches here.
97
+ */
98
+ const body = await streamToBuffer(streamWithUploadEvents, dataItemSize, resume, signal);
99
+ return this.httpService.post({
100
+ endpoint: `/tx/${this.token}`,
101
+ signal,
102
+ data: body,
103
+ headers,
104
+ x402Options,
105
+ });
106
+ }
83
107
  // setup the post request using the stream with upload events
84
108
  const postPromise = this.httpService.post({
85
109
  endpoint: `/tx/${this.token}`,
@@ -92,6 +116,46 @@ class TurboUnauthenticatedUploadService {
92
116
  resume();
93
117
  return postPromise;
94
118
  }
119
+ /**
120
+ * Price a SIGNED data item for an x402 upload, without sending it.
121
+ *
122
+ * The only other way to learn an x402 price is to POST the payload and read
123
+ * the 402 challenge, which means transmitting it to find out what it costs.
124
+ *
125
+ * `byteCount` is the size of the signed data item — what
126
+ * `dataItemSizeFactory()` returns — not the payload inside it. Use
127
+ * `getX402PriceForRawData` when the service does the wrapping.
128
+ *
129
+ * `network` names the x402 network, not the SDK token: the route builds its
130
+ * token as `usdc-{network}`. `base-usdc` is accepted on mainnet only because
131
+ * the network there is literally `base`, so a testnet caller must pass
132
+ * `base-sepolia` explicitly.
133
+ */
134
+ async getX402PriceForDataItem({ byteCount, network = 'base', }) {
135
+ return this.httpService.get({
136
+ endpoint: `/price/x402/data-item/usdc-${network}/${byteCount}`,
137
+ });
138
+ }
139
+ /**
140
+ * Price RAW data for an x402 upload, where the service wraps it into a data
141
+ * item itself.
142
+ *
143
+ * Also reports the wrapping overhead — a data item is larger than its
144
+ * payload by its header, signature and tags — which the caller has no way to
145
+ * compute. `tagCount` and `contentType` feed that estimate, so pass what the
146
+ * upload will actually carry or the quote will read low.
147
+ */
148
+ async getX402PriceForRawData({ byteCount, network = 'base', tagCount, contentType, }) {
149
+ const query = new URLSearchParams();
150
+ if (tagCount !== undefined)
151
+ query.set('tags', `${tagCount}`);
152
+ if (contentType !== undefined)
153
+ query.set('contentType', contentType);
154
+ const qs = query.toString();
155
+ return this.httpService.get({
156
+ endpoint: `/price/x402/data/usdc-${network}/${byteCount}${qs ? `?${qs}` : ''}`,
157
+ });
158
+ }
95
159
  async uploadRawX402Data({ data, tags, signal, maxMUSDCAmount, signer, }) {
96
160
  if (!this.x402EnabledTokens.includes(this.token)) {
97
161
  throw new Error('x402 uploads are not supported for token: ' + this.token);
@@ -226,8 +290,28 @@ class TurboAuthenticatedBaseUploadService extends TurboUnauthenticatedUploadServ
226
290
  !this.x402EnabledTokens.includes(this.token)) {
227
291
  throw new Error('x402 uploads are not supported for token: ' + this.token);
228
292
  }
293
+ /*
294
+ Buffering the single request is only safe because anything larger chunks.
295
+ `chunkingMode: 'disabled'` removes that guarantee, so an arbitrarily large
296
+ item would be pulled into memory — and the service caps single-request
297
+ items anyway, so it would be refused after the fact. Check before signing:
298
+ signing a large item only to reject it wastes the expensive part.
299
+ */
300
+ if (fundingMode instanceof types_js_1.X402Funding &&
301
+ params.chunkingMode === 'disabled' &&
302
+ fileSizeFactory() > maxX402SingleRequestByteCount) {
303
+ throw new Error(`An x402 upload of ${fileSizeFactory()} bytes must be chunked: the ` +
304
+ `single-request path buffers the item in memory and is limited to ` +
305
+ `${maxX402SingleRequestByteCount} bytes. Remove ` +
306
+ `chunkingMode: 'disabled' to upload this item.`);
307
+ }
229
308
  this.logger.debug('Starting file upload', { params });
230
309
  let retries = 0;
310
+ // Carried ACROSS retry attempts. A paid chunked upload is bought before any
311
+ // chunk is accepted, so an attempt that fails after paying must hand its
312
+ // upload id to the next attempt. Rebuilding the uploader with no id is how
313
+ // a single upload gets billed once per retry.
314
+ let paidUploadId;
231
315
  const maxRetries = this.retryConfig.retries ?? 3;
232
316
  const retryDelay = this.retryConfig.retryDelay ??
233
317
  ((retryNumber) => retryNumber * 1000);
@@ -283,6 +367,7 @@ class TurboAuthenticatedBaseUploadService extends TurboUnauthenticatedUploadServ
283
367
  dataItemByteCount: dataItemSizeFactory(),
284
368
  chunkingMode: params.chunkingMode,
285
369
  maxFinalizeMs: params.maxFinalizeMs,
370
+ paidUploadId,
286
371
  ...(x402Options ? { x402: x402Options } : {}),
287
372
  ...(x402Options
288
373
  ? {
@@ -294,14 +379,21 @@ class TurboAuthenticatedBaseUploadService extends TurboUnauthenticatedUploadServ
294
379
  : {}),
295
380
  });
296
381
  if (chunkedUploader.shouldUseChunkUploader) {
297
- const response = await chunkedUploader.upload({
298
- dataItemStreamFactory,
299
- dataItemSizeFactory,
300
- dataItemOpts,
301
- signal,
302
- events,
303
- });
304
- return { ...response, cryptoFundResult };
382
+ try {
383
+ const response = await chunkedUploader.upload({
384
+ dataItemStreamFactory,
385
+ dataItemSizeFactory,
386
+ dataItemOpts,
387
+ signal,
388
+ events,
389
+ });
390
+ return { ...response, cryptoFundResult };
391
+ }
392
+ finally {
393
+ // Read on the way out, success or failure. On failure this is the
394
+ // whole point: the next attempt resumes what this one paid for.
395
+ paidUploadId = chunkedUploader.currentPaidUploadId ?? paidUploadId;
396
+ }
305
397
  }
306
398
  const response = await this.uploadSignedDataItem({
307
399
  dataItemStreamFactory,
@@ -983,3 +1075,88 @@ class TurboAuthenticatedBaseUploadService extends TurboUnauthenticatedUploadServ
983
1075
  }
984
1076
  }
985
1077
  exports.TurboAuthenticatedBaseUploadService = TurboAuthenticatedBaseUploadService;
1078
+ /**
1079
+ * The largest data item the x402 single-request path will buffer.
1080
+ *
1081
+ * Normally unreachable: anything over two chunks takes the chunked path, which
1082
+ * pays at create and streams. It bites only when chunking is explicitly
1083
+ * disabled, and it exists so that case fails with an explanation rather than
1084
+ * by exhausting memory.
1085
+ */
1086
+ const maxX402SingleRequestByteCount = 100 * 1024 * 1024;
1087
+ /**
1088
+ * Drain a data-item stream into a Buffer, resuming it once the reader is
1089
+ * attached so upload-progress events still fire.
1090
+ *
1091
+ * Checks the running total as it fills rather than at the end: a stream that
1092
+ * overruns its declared size would otherwise be held in memory in full before
1093
+ * anything objected.
1094
+ */
1095
+ async function streamToBuffer(stream, byteCount, resume, signal) {
1096
+ // A closure, so TypeScript does not narrow `aborted` away for the whole
1097
+ // function — it genuinely can flip while the stream is draining.
1098
+ const isAborted = () => signal?.aborted === true;
1099
+ if (isAborted()) {
1100
+ throw new errors_js_1.AbortError();
1101
+ }
1102
+ const chunks = [];
1103
+ let received = 0;
1104
+ const take = (chunk) => {
1105
+ received += chunk.byteLength;
1106
+ if (received > byteCount) {
1107
+ throw new Error(`Data item stream exceeded its declared size of ${byteCount} bytes`);
1108
+ }
1109
+ chunks.push(chunk);
1110
+ };
1111
+ if (stream instanceof node_stream_1.Readable) {
1112
+ const done = new Promise((resolve, reject) => {
1113
+ const onAbort = () => {
1114
+ stream.destroy();
1115
+ reject(new errors_js_1.AbortError());
1116
+ };
1117
+ signal?.addEventListener('abort', onAbort, { once: true });
1118
+ stream.on('data', (c) => {
1119
+ try {
1120
+ take(Buffer.from(c));
1121
+ }
1122
+ catch (error) {
1123
+ stream.destroy();
1124
+ reject(error);
1125
+ }
1126
+ });
1127
+ stream.on('end', () => {
1128
+ signal?.removeEventListener('abort', onAbort);
1129
+ resolve();
1130
+ });
1131
+ stream.on('error', reject);
1132
+ });
1133
+ resume();
1134
+ await done;
1135
+ }
1136
+ else {
1137
+ const reader = stream.getReader();
1138
+ resume();
1139
+ try {
1140
+ for (;;) {
1141
+ if (isAborted()) {
1142
+ await reader.cancel();
1143
+ throw new errors_js_1.AbortError();
1144
+ }
1145
+ const { done, value } = await reader.read();
1146
+ if (done)
1147
+ break;
1148
+ if (value !== undefined)
1149
+ take(Buffer.from(value));
1150
+ }
1151
+ }
1152
+ catch (error) {
1153
+ await reader.cancel().catch(() => undefined);
1154
+ throw error;
1155
+ }
1156
+ }
1157
+ const buf = Buffer.concat(chunks);
1158
+ if (buf.byteLength !== byteCount) {
1159
+ throw new Error(`Data item stream produced ${buf.byteLength} bytes, expected ${byteCount}`);
1160
+ }
1161
+ return buf;
1162
+ }
@@ -35,13 +35,14 @@ const chunkingHeader = { 'x-chunking-version': '2' };
35
35
  * uploading them in parallel, and emitting progress/error events.
36
36
  */
37
37
  export class ChunkedUploader {
38
- constructor({ http, token, maxChunkConcurrency = defaultMaxChunkConcurrency, maxFinalizeMs, chunkByteCount = defaultChunkByteCount, logger = Logger.default, chunkingMode = 'auto', dataItemByteCount, x402, x402RefundIdentity, }) {
38
+ constructor({ http, token, maxChunkConcurrency = defaultMaxChunkConcurrency, maxFinalizeMs, chunkByteCount = defaultChunkByteCount, logger = Logger.default, chunkingMode = 'auto', dataItemByteCount, x402, x402RefundIdentity, paidUploadId, }) {
39
39
  this.assertChunkParams({
40
40
  chunkByteCount,
41
41
  chunkingMode,
42
42
  maxChunkConcurrency,
43
43
  maxFinalizeMs,
44
44
  });
45
+ this.paidUploadId = paidUploadId;
45
46
  this.chunkByteCount = chunkByteCount;
46
47
  this.maxChunkConcurrency = maxChunkConcurrency;
47
48
  this.maxFinalizeMs = maxFinalizeMs;
@@ -139,6 +140,14 @@ export class ChunkedUploader {
139
140
  this.paidUploadId = res.id;
140
141
  return res.id;
141
142
  }
143
+ /**
144
+ * The paid upload id, once one has been bought. `uploadFile` reads this after
145
+ * a failed attempt so the next attempt resumes the upload already paid for
146
+ * rather than buying a second one.
147
+ */
148
+ get currentPaidUploadId() {
149
+ return this.paidUploadId;
150
+ }
142
151
  async initUpload() {
143
152
  if (this.x402 !== undefined) {
144
153
  return this.initPaidUpload(this.x402);
@@ -48,8 +48,21 @@ export class TurboHTTPService {
48
48
  this.baseURL = url;
49
49
  this.retryConfig = retryConfig;
50
50
  }
51
+ /**
52
+ * Refuse to pay over cleartext.
53
+ *
54
+ * An x402 payment authorization is a bearer credential: anyone who observes
55
+ * it can submit it. Sending one over `http:` hands it to the network, so a
56
+ * misconfigured service URL must fail loudly rather than quietly leak.
57
+ */
58
+ assertSecureForPayment() {
59
+ if (!this.baseURL.startsWith('https:') && !isLoopback(this.baseURL)) {
60
+ throw new Error(`Refusing to send an x402 payment over a non-HTTPS URL: ${this.baseURL}`);
61
+ }
62
+ }
51
63
  async get({ endpoint, signal, allowedStatuses = [200, 202], headers, x402Options, }) {
52
64
  if (x402Options !== undefined) {
65
+ this.assertSecureForPayment();
53
66
  const maxMUSDCAmount = x402Options.maxMUSDCAmount !== undefined
54
67
  ? BigInt(x402Options.maxMUSDCAmount.toString())
55
68
  : undefined;
@@ -158,6 +171,7 @@ export class TurboHTTPService {
158
171
  endpoint,
159
172
  x402Options,
160
173
  });
174
+ this.assertSecureForPayment();
161
175
  const { body, duplex } = await toFetchBody(data);
162
176
  return this.tryRequest(async () => {
163
177
  const maxMUSDCAmount = x402Options.maxMUSDCAmount !== undefined
@@ -208,3 +222,13 @@ function isFirefoxOrSafari() {
208
222
  !ua.includes('Chrome') &&
209
223
  !ua.includes('Chromium')));
210
224
  }
225
+ /** Loopback is exempt: local development never leaves the machine. */
226
+ function isLoopback(url) {
227
+ try {
228
+ const { hostname } = new URL(url);
229
+ return (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1');
230
+ }
231
+ catch {
232
+ return false;
233
+ }
234
+ }
@@ -653,13 +653,20 @@ export class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymen
653
653
  * The owner needs a Solana key to sign with, NOT a funded one — Turbo pays
654
654
  * every lamport of fee and rent.
655
655
  */
656
- async buyArNSName({ name, owner, type = 'lease', years, paidBy, onNonce, }) {
656
+ async buyArNSName({ name, owner, type = 'lease', years, paidBy, onNonce, antState, }) {
657
657
  return this.completeArNSAction('buy-name', {
658
658
  name,
659
659
  ownerAddress: await owner.getAddress(),
660
660
  type,
661
661
  ...(years !== undefined ? { years } : {}),
662
662
  ...(paidBy !== undefined ? { paidBy } : {}),
663
+ /*
664
+ Spread conditionally, like `paidBy` — an `antState: undefined` key
665
+ would serialize into the body and is not the same request as omitting
666
+ it. Sent whole in the BODY rather than the query string: it is an
667
+ object, and `createArNSAction` already posts every param as JSON.
668
+ */
669
+ ...(antState !== undefined ? { antState } : {}),
663
670
  }, owner, { onNonce });
664
671
  }
665
672
  /** Extend a lease. Permissionless on chain — no owner signature needed. */
@@ -190,6 +190,20 @@ export class TurboUnauthenticatedClient {
190
190
  maxMUSDCAmount,
191
191
  });
192
192
  }
193
+ /**
194
+ * Price a signed data item for an x402 upload without sending it. See
195
+ * `TurboUnauthenticatedUploadService.getX402PriceForDataItem`.
196
+ */
197
+ getX402PriceForDataItem(p) {
198
+ return this.uploadService.getX402PriceForDataItem(p);
199
+ }
200
+ /**
201
+ * Price raw data for an x402 upload, including the data-item wrapping
202
+ * overhead. See `TurboUnauthenticatedUploadService.getX402PriceForRawData`.
203
+ */
204
+ getX402PriceForRawData(p) {
205
+ return this.uploadService.getX402PriceForRawData(p);
206
+ }
193
207
  }
194
208
  export class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
195
209
  constructor({ paymentService, uploadService, signer, }) {
@@ -377,4 +391,18 @@ export class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
377
391
  maxMUSDCAmount,
378
392
  });
379
393
  }
394
+ /**
395
+ * Price a signed data item for an x402 upload without sending it. See
396
+ * `TurboUnauthenticatedUploadService.getX402PriceForDataItem`.
397
+ */
398
+ getX402PriceForDataItem(p) {
399
+ return this.uploadService.getX402PriceForDataItem(p);
400
+ }
401
+ /**
402
+ * Price raw data for an x402 upload, including the data-item wrapping
403
+ * overhead. See `TurboUnauthenticatedUploadService.getX402PriceForRawData`.
404
+ */
405
+ getX402PriceForRawData(p) {
406
+ return this.uploadService.getX402PriceForRawData(p);
407
+ }
380
408
  }