diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index e40e18b..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/packages/fil/CHANGELOG.md b/packages/fil/CHANGELOG.md index ab28f52..569d106 100644 --- a/packages/fil/CHANGELOG.md +++ b/packages/fil/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 1.0.0 + +### Major Changes + +- upload flow now pins to pinata at deposit time — the file upload step after confirm is no longer needed and has been removed + + breaking: deposit response no longer includes depositMetadata + breaking: confirm no longer accepts depositMetadata in the request body + breaking: url is now returned from the confirm/verify-payment response instead of the file upload response + ## 0.2.2 ### Patch Changes diff --git a/packages/fil/package.json b/packages/fil/package.json index 281a235..e9a4dae 100644 --- a/packages/fil/package.json +++ b/packages/fil/package.json @@ -1,6 +1,6 @@ { "name": "@toju.network/fil", - "version": "0.2.2", + "version": "1.0.0", "description": "Pay for storage with USDFC on IPFS using Storacha's Hot storage network.", "license": "Apache-2.0", "type": "module", diff --git a/packages/fil/src/payment.ts b/packages/fil/src/payment.ts index 53a7e13..e221909 100644 --- a/packages/fil/src/payment.ts +++ b/packages/fil/src/payment.ts @@ -63,7 +63,6 @@ export async function createDepositTxn( { transactionHash: txHash, cid: depositRes.cid, - depositMetadata: depositRes.depositMetadata, }, apiEndpoint, ) @@ -73,55 +72,12 @@ export async function createDepositTxn( verifyRes.message || 'Payment verification failed on server', ) - const uploadData = new FormData() - file.forEach((file) => uploadData.append('file', file)) - let fileUploadReq - const isMultipleFiles = file.length > 1 - - if (isMultipleFiles) { - fileUploadReq = await fetch( - `${apiEndpoint}/upload/files?cid=${encodeURIComponent(depositRes.cid)}`, - { - method: 'POST', - body: uploadData, - }, - ) - } else { - fileUploadReq = await fetch( - `${apiEndpoint}/upload/file?cid=${encodeURIComponent(depositRes.cid)}`, - { - method: 'POST', - body: uploadData, - }, - ) - } - - if (!fileUploadReq.ok) { - let err = 'Unknown error' - try { - const data: DepositResponse = await fileUploadReq.json() - err = data.message || err - } catch {} - throw new Error('Deposit API error: ' + err) - } - - const uploadResponse: Pick = - await fileUploadReq?.json() - return { success: true, transactionHash: txHash, cid: depositRes.cid, - url: uploadResponse.object.url, - message: uploadResponse.object.message, - fileInfo: uploadResponse.object - ? { - type: uploadResponse?.object?.fileInfo?.type || '', - size: uploadResponse?.object?.fileInfo?.size || 0, - uploadedAt: uploadResponse?.object?.fileInfo?.uploadedAt || '', - filename: uploadResponse?.object?.fileInfo?.filename || '', - } - : undefined, + url: verifyRes.url || '', + message: verifyRes.message || '', } } catch (error) { const errorMessage = @@ -155,7 +111,7 @@ export async function verifyPayment( args: VerifyPaymentArgs, apiEndpoint: string, ): Promise { - const { transactionHash, cid, depositMetadata } = args + const { transactionHash, cid } = args const verifyReq = await fetch(`${apiEndpoint}/upload/fil/verify-payment`, { method: 'POST', @@ -165,7 +121,6 @@ export async function verifyPayment( body: JSON.stringify({ transactionHash, cid, - depositMetadata, }), }) diff --git a/packages/fil/src/types.ts b/packages/fil/src/types.ts index de172ef..de8a29f 100644 --- a/packages/fil/src/types.ts +++ b/packages/fil/src/types.ts @@ -82,8 +82,6 @@ export interface DepositResponse { size: number type: string }> - /** Metadata for database insertion */ - depositMetadata: DepositMetadata } /** @@ -124,8 +122,6 @@ export interface VerifyPaymentArgs { transactionHash: string /** CID of the uploaded content */ cid: string - /** Deposit metadata from server response */ - depositMetadata: DepositMetadata } /** @@ -140,6 +136,8 @@ export interface VerifyPaymentResponse { transactionHash: string /** CID of the uploaded content */ cid: string + /** Gateway URL to access the file */ + url?: string } /** diff --git a/packages/sol/CHANGELOG.md b/packages/sol/CHANGELOG.md index 614621c..b349fc6 100644 --- a/packages/sol/CHANGELOG.md +++ b/packages/sol/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.0.0 + +### Major Changes + +- upload flow now pins to pinata at deposit time — the file upload step after confirm is no longer needed and has been removed + + breaking: deposit response no longer includes depositMetadata + breaking: confirm no longer accepts depositMetadata in the request body + breaking: url is now returned from the confirm/verify-payment response instead of the file upload response + +### Patch Changes + +- 38a0367: renewal response now returns the gateway url from the server instead of a hardcoded w3s.link url + ## 0.1.6 ### Patch Changes diff --git a/packages/sol/package.json b/packages/sol/package.json index bc6b985..b93c7bf 100644 --- a/packages/sol/package.json +++ b/packages/sol/package.json @@ -1,6 +1,6 @@ { "name": "@toju.network/sol", - "version": "0.1.6", + "version": "1.0.0", "type": "module", "module": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/sol/src/payment.ts b/packages/sol/src/payment.ts index 403e565..d61734b 100644 --- a/packages/sol/src/payment.ts +++ b/packages/sol/src/payment.ts @@ -40,10 +40,6 @@ export async function createDepositTxn( if (userEmail) formData.append('userEmail', userEmail) if (args.directoryName) formData.append('directoryName', args.directoryName) - const isMultipleFiles = file.length > 1 - - let _uploadErr - const depositReq = await fetch(`${apiEndpoint}/upload/deposit`, { method: 'POST', body: formData, @@ -94,7 +90,9 @@ export async function createDepositTxn( ) throw new Error( - 'Transaction failed during simulation. Please try again.', + logs.length + ? `Transaction simulation failed:\n${logs.join('\n')}` + : 'Transaction failed during simulation. Please try again.', ) } @@ -129,7 +127,6 @@ export async function createDepositTxn( body: JSON.stringify({ cid: depositRes.cid, transactionHash: signature, - depositMetadata: depositRes.depositMetadata, }), }) @@ -146,59 +143,14 @@ export async function createDepositTxn( ) } - if (depositRes.error) { - _uploadErr = depositRes.error - } - - const uploadForm = new FormData() - file.forEach((f) => uploadForm.append('file', f)) - - let fileUploadReq - // calls the upload functionality on our server with the file when deposit is successful - if (isMultipleFiles) { - fileUploadReq = await fetch( - `${apiEndpoint}/upload/files?cid=${encodeURIComponent(depositRes.cid)}`, - { - method: 'POST', - body: uploadForm, - }, - ) - } else { - fileUploadReq = await fetch( - `${apiEndpoint}/upload/file?cid=${encodeURIComponent(depositRes.cid)}`, - { - method: 'POST', - body: uploadForm, - }, - ) - } - - if (!fileUploadReq.ok) { - let err = 'Unknown error' - try { - const data: DepositResult = await fileUploadReq.json() - err = data.message || data.error || err - } catch {} - throw new Error('Deposit API error: ' + err) - } - - const fileUploadRes: Pick = - await fileUploadReq?.json() + const confirmData = await confirmRes.json().catch(() => ({})) return { signature: signature as Signature, success: true, cid: depositRes.cid, - url: fileUploadRes.object.url, - message: fileUploadRes.object.message, - fileInfo: fileUploadRes.object - ? { - filename: fileUploadRes.object.fileInfo?.filename || '', - size: fileUploadRes?.object?.fileInfo?.size || 0, - uploadedAt: fileUploadRes?.object?.fileInfo?.uploadedAt || '', - type: fileUploadRes?.object?.fileInfo?.type || '', - } - : undefined, + url: confirmData?.url || '', + message: confirmData?.message || '', } } catch (error) { console.error(error) @@ -336,15 +288,14 @@ export async function renewStorageTxn( }, ) - if (!confirmRenewalTx.ok) { - console.error('Failed to confirm renewal') - } + if (!confirmRenewalTx.ok) console.error('Failed to confirm renewal') + const confirmationData = await confirmRenewalTx.json().catch(() => ({})) return { success: true, cid, signature: signature as Signature, - url: `https://w3s.link/ipfs/${cid}`, + url: confirmationData?.url || '', message: 'Storage renewed successfully', } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eeec31e..0d569ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 2.4.9 '@changesets/cli': specifier: ^2.30.0 - version: 2.30.0(@types/node@25.5.0) + version: 2.30.0(@types/node@25.5.2) husky: specifier: ^9.1.7 version: 9.1.7 @@ -32,7 +32,7 @@ importers: devDependencies: knip: specifier: ^5.88.1 - version: 5.88.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(typescript@5.9.3) + version: 5.88.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(typescript@5.9.3) tsup: specifier: ^8.5.1 version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) @@ -51,7 +51,7 @@ importers: devDependencies: knip: specifier: ^5.88.1 - version: 5.88.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(typescript@5.9.3) + version: 5.88.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(typescript@5.9.3) tsup: specifier: ^8.5.1 version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) @@ -63,13 +63,13 @@ importers: dependencies: '@x402/core': specifier: ^2.8.0 - version: 2.8.0 + version: 2.9.0 '@x402/evm': specifier: ^2.8.0 - version: 2.8.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + version: 2.9.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@x402/fetch': specifier: ^2.8.0 - version: 2.8.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + version: 2.9.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) viem: specifier: ~2.45.3 version: 2.45.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) @@ -85,10 +85,10 @@ importers: dependencies: '@coinbase/x402': specifier: ^2.1.0 - version: 2.1.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 2.1.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) '@coral-xyz/anchor': specifier: ^0.31.1 - version: 0.31.1(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 0.31.1(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6) '@logtail/node': specifier: ^0.5.8 version: 0.5.8 @@ -106,31 +106,22 @@ importers: version: 10.46.0 '@solana/web3.js': specifier: ^1.98.4 - version: 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@storacha/client': - specifier: ^2.1.3 - version: 2.1.3(encoding@0.1.13) + version: 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6) '@types/bn.js': specifier: ^5.2.0 version: 5.2.0 - '@ucanto/core': - specifier: ^10.4.6 - version: 10.4.6 '@upstash/qstash': specifier: ^2.10.1 version: 2.10.1 '@x402/core': specifier: ^2.8.0 - version: 2.8.0 + version: 2.9.0 '@x402/evm': specifier: ^2.8.0 - version: 2.8.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + version: 2.9.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@x402/express': specifier: ^2.8.0 - version: 2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(express@5.2.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - axios: - specifier: ^1.14.0 - version: 1.14.0 + version: 2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(express@5.2.1)(typescript@5.9.3)(utf-8-validate@6.0.6) bn.js: specifier: ^5.2.3 version: 5.2.3 @@ -139,7 +130,7 @@ importers: version: 2.8.6 dotenv: specifier: ^17.3.1 - version: 17.3.1 + version: 17.4.1 drizzle-orm: specifier: ^0.44.7 version: 0.44.7(@neondatabase/serverless@1.0.2)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.20.0)(postgres@3.4.8) @@ -167,6 +158,9 @@ importers: pg: specifier: ^8.20.0 version: 8.20.0 + pinata: + specifier: 2.5.5 + version: 2.5.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) postgres: specifier: ^3.4.8 version: 3.4.8 @@ -268,11 +262,11 @@ importers: specifier: ^1.167.9 version: 1.167.9(@tanstack/react-router@1.168.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@toju.network/fil': - specifier: ^0.2.2 - version: 0.2.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) + specifier: ^1.0.0 + version: 1.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@toju.network/sol': - specifier: ^0.1.6 - version: 0.1.6(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + specifier: ^1.0.0 + version: 1.0.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) dayjs: specifier: ^1.11.20 version: 1.11.20 @@ -360,32 +354,32 @@ importers: dependencies: '@x402/core': specifier: latest - version: 2.8.0 + version: 2.9.0 '@x402/evm': specifier: latest - version: 2.8.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6) + version: 2.9.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6) '@x402/express': specifier: latest - version: 2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(express@5.2.1)(typescript@6.0.2)(utf-8-validate@6.0.6) + version: 2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(express@5.2.1)(typescript@6.0.2)(utf-8-validate@6.0.6) '@x402/fetch': specifier: latest - version: 2.8.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6) + version: 2.9.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6) dotenv: specifier: latest - version: 17.3.1 + version: 17.4.1 express: specifier: latest version: 5.2.1 viem: specifier: latest - version: 2.47.6(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 2.47.10(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@4.3.6) devDependencies: '@types/express': specifier: latest version: 5.0.6 '@types/node': specifier: latest - version: 25.5.0 + version: 25.5.2 tsx: specifier: latest version: 4.21.0 @@ -1447,9 +1441,6 @@ packages: resolution: {integrity: sha512-w4PZ2yPqvNmlAir7/2hsCRMqny1EY5jj26iZcSgxREJexmbAc2FI21jp26MqiNdfgAxvkCnf2N/TJI18GaDNwA==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} - '@ipld/dag-ucan@3.4.5': - resolution: {integrity: sha512-wiWhH0Ju7WgnumsarHCYtlo+1NRy+WIsXyAB7g2sDqVsKCyEJiLLmjeZzKqNv+qMxLnUS8bQ287cPiQ//s/oJQ==} - '@ipld/unixfs@3.0.0': resolution: {integrity: sha512-Tj3/BPOlnemcZQ2ETIZAO8hqAs9KNzWyX5J9+JCL9jDwvYwjxeYjqJ3v+9DusNvTBmJhZnGVP6ijUHrsuOLp+g==} @@ -1722,9 +1713,6 @@ packages: resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} engines: {node: '>= 20.19.0'} - '@noble/ed25519@1.7.5': - resolution: {integrity: sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA==} - '@noble/hashes@1.3.2': resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} engines: {node: '>= 16'} @@ -3579,34 +3567,6 @@ packages: resolution: {integrity: sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==} engines: {node: '>=20.0.0'} - '@storacha/access@1.6.9': - resolution: {integrity: sha512-hdEyVg6gvYmYbKN1K3h+U7m/BbTwxnegMfi9ZJm/7D7Vk3kuttURv49DmsmfH/LPHCqAiP1+pEcFUzkZv4oAKg==} - - '@storacha/blob-index@1.2.9': - resolution: {integrity: sha512-6tsnh4c/bTNJGKw4uuXZGTVYcxy19jdsK/DHE4aZajPha96bW9RnZTxLO1e0VIXPWNBXo/Hvxb0nik+OfTQHkw==} - engines: {node: '>=16.15'} - hasBin: true - - '@storacha/capabilities@2.3.1': - resolution: {integrity: sha512-cXSdRdqZoyKpIAHMvr+rqJpS+xdJ/n8e3w7CIbswuaHVo+vj54PeGOLm3YuQA+K7AX1GEn2mPVmW1V192NmBwg==} - - '@storacha/client@2.1.3': - resolution: {integrity: sha512-TpIzSPZpz3WpYzeRhVgm/WhrFwLZdvy2jrU3p27S5mf4ZP6kpMfbUQI02f8a2eIRzjdT4e6YuQ1qn3wbQsNj9Q==} - engines: {node: '>=18'} - - '@storacha/did-mailto@1.0.2': - resolution: {integrity: sha512-0ee1t15yG4xdyIYz2iL+wP06VnGQf8zJ/xpszjf4sdP2sTgAVzx92LfEgHcuIbFF4sabcJXzHFAcMi8l8u64Mg==} - engines: {node: '>=16.15'} - - '@storacha/filecoin-client@1.0.20': - resolution: {integrity: sha512-vcc2490ji1mveErErPFKYuX0gf0D+RbloxiArd3v0uPkPfgO/xxZG/gtMQeq2EwAbHD2BRVD+X4lxHUOlVoDZg==} - - '@storacha/one-webcrypto@1.0.1': - resolution: {integrity: sha512-bD+vWmcgsEBqU0Dz04BR43SA03bBoLTAY29vaKasY9Oe8cb6XIP0/vkm0OS2UwKC13c8uRgFW4rjJUgDCNLejQ==} - - '@storacha/upload-client@1.4.2': - resolution: {integrity: sha512-vXn6ZMa39DXOJL0IbnxOfLDPHzA7CV1zmGk3mXr4bHhvAJekgWiZq+KluiMt9AfumnpPI7Nknrs0ZmUnwzrOYQ==} - '@stylistic/eslint-plugin@5.10.0': resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3760,11 +3720,11 @@ packages: engines: {node: '>=20.19'} hasBin: true - '@toju.network/fil@0.2.2': - resolution: {integrity: sha512-m7pmCI/IftEFZat4qbwTTFzcMswbjANErh55U5j4xtjalHI0qYNRgb73fk0G87RMLOxF01NMmPVuVGdhk0sRGg==} + '@toju.network/fil@1.0.0': + resolution: {integrity: sha512-LeiHr9oL3vSLKeQzFocjaRG+QEkI8t7Jqz3xkark1vchOUAo06M3BKKgASeph+DBC/Z9tQvG7oIE1rRJUh9tdQ==} - '@toju.network/sol@0.1.6': - resolution: {integrity: sha512-lpq4neIS7Lw8oQN+IMUhcNtJxwYhCaAwHEIz4tT9lGWmsW4PEiSSPICWxDcfHXdNh37C1Bfrnr3Cgrx6RX22ug==} + '@toju.network/sol@1.0.0': + resolution: {integrity: sha512-hbsHVkNhcLxUMMCswYsO/Uuah/NQ3aIZjgS32ClYJGiqfS81BuDPz+nSBg//yewl1D91wSaIuPRll+zcM4rz+g==} '@toruslabs/base-controllers@5.11.0': resolution: {integrity: sha512-5AsGOlpf3DRIsd6PzEemBoRq+o2OhgSFXj5LZD6gXcBlfe0OpF+ydJb7Q8rIt5wwpQLNJCs8psBUbqIv7ukD2w==} @@ -4024,9 +3984,6 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/minimatch@3.0.5': - resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - '@types/morgan@1.9.10': resolution: {integrity: sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==} @@ -4051,8 +4008,8 @@ packages: '@types/node@24.12.0': resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} - '@types/node@25.5.0': - resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/node@25.5.2': + resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -4080,9 +4037,6 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@types/retry@0.12.1': - resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==} - '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} @@ -4190,24 +4144,6 @@ packages: resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ucanto/client@9.0.2': - resolution: {integrity: sha512-XbG8rw/fc1hPSFKDQ/z1NpIuc4Lm79J4NWbP+dedvDnb5LmOBMHE8bjjEPD4xGO2eh6fWQSZ4YTYk5wjkLz+3Q==} - - '@ucanto/core@10.4.6': - resolution: {integrity: sha512-K3aRsbolL3Mas1P+7Ba+kpCvEVlBJf93V4OZ23FOZRBnbodD+x/ygOEXWL8OYcZpBwYiPeYWMAJPZpIzjNIEoQ==} - - '@ucanto/interface@11.0.1': - resolution: {integrity: sha512-d0yoPFXdbb3EyM3sBom5VVcVyz58HT+57qi/ywSY1dbZAm4c7GZss0lpfcI5OXQREzGCzJYAg1e7pFwqbATnmQ==} - - '@ucanto/principal@9.0.3': - resolution: {integrity: sha512-MFLvckOpQA46VSeLWyB7xCysL+ub5vnbCEtHbWhNE3qkzeo14v4wSThd60odPhxj83QXrzbveiebp0gcaEvXJg==} - - '@ucanto/transport@9.2.1': - resolution: {integrity: sha512-/Dhr+2vjpGO5GTKsSl8XcPQFtFdZvIA9Yk/yzzY9DHFsdI+tXrUjLWYwXcidx8EIdx5Xd9oitF4wodIZuiW8GQ==} - - '@ucanto/validator@10.0.1': - resolution: {integrity: sha512-J1/2NAfvs55CxDEuHKMTjoLgoVrFqBbZqErYwL7TDx8Pu2V9sv7rc2wZS5dRxcM1VCEzHXY3SWeLOJuH8VgKrw==} - '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -4513,29 +4449,26 @@ packages: '@web3-storage/car-block-validator@1.2.2': resolution: {integrity: sha512-lR9l+ZszhTid5HfZE8ohnGf2RJp2kaBOnoejmsACs3iTNiy+3K09dnPm8MhgBE9RCIgPBKM0CCWXO9l+I6jrKA==} - '@web3-storage/data-segment@5.3.0': - resolution: {integrity: sha512-zFJ4m+pEKqtKatJNsFrk/2lHeFSbkXZ6KKXjBe7/2ayA9wAar7T/unewnOcZrrZTnCWmaxKsXWqdMFy9bXK9dw==} + '@x402/core@2.9.0': + resolution: {integrity: sha512-IqPITHYx6XHlgLPtparuKKwoB+3wQdgt0F+WUH1e3WHMeiWdp+xTtQDy+6yOKuObNFI1S1iVbQFz0GivR/Vv3w==} - '@x402/core@2.8.0': - resolution: {integrity: sha512-ppsvSzyWzlqG+26dcNzOXo/YLaHreWc3lmdv9W81mEhLDWMdPpiGyRjSUyO9BQFuQJMQppvqA7ujUbLE/EaDkg==} + '@x402/evm@2.9.0': + resolution: {integrity: sha512-qUhnKe1pym9a+7dzeK+6ripsddVsr+5PNcpQfTYK4dubW+1SR9MRx/O4PNRtedWoAxminqAwmCL5AQUiSVvKWA==} - '@x402/evm@2.8.0': - resolution: {integrity: sha512-/w1WvoXIZdaJcmvR8p9/ow1n8GjNWeeVnpoorjfLv2oBbT06r33clcfYwhH/COJp/K0aOvmol0elx5h2IQCnOA==} - - '@x402/express@2.8.0': - resolution: {integrity: sha512-N+lQWuwGKRWnpXVICkoHA8IncO6cfs/ZEzdhW839vOoFMTj4JiqyL6MUw4WMpmpJ4D+ZFe1eCTnYY5A7iaMB7A==} + '@x402/express@2.9.0': + resolution: {integrity: sha512-E236c188p7rkAILnzdd19FQZuTEOM9+DP/y/FG4B0oawiBXx5FGR5ez7EUKCG7V7FxXsGvMEjUFMJ0BjrQx8yw==} peerDependencies: - '@x402/paywall': ^2.8.0 + '@x402/paywall': ^2.9.0 express: ^4.0.0 || ^5.0.0 peerDependenciesMeta: '@x402/paywall': optional: true - '@x402/extensions@2.8.0': - resolution: {integrity: sha512-3VHY7cjyYkI8JMbF81Soh9gEcFFekXiGr+71Jp39lOzIGmX67Q8+2hkLiIu5laKoaz0vi2gnaqTYESADRhO9DA==} + '@x402/extensions@2.9.0': + resolution: {integrity: sha512-Fk7MJFWSQtsRGGZcNpgwv8V6IG38USNciA5ImkF0InceWDfjMBq6b8GmksM+cVHAXQsMQYxw3HV8uhfwTPEGAQ==} - '@x402/fetch@2.8.0': - resolution: {integrity: sha512-GEY6DS5iy/PxwfbdfIBY0RsxrHnHylKmXMqzL8BJdSaerm3w5XhTkNCOG6JyaIt1CpQccYXbzlOEpqFJQNaNAA==} + '@x402/fetch@2.9.0': + resolution: {integrity: sha512-/ocFWG5aTftCabW4CWXLZjafzTwkmW+c7u4lzXLegJ7F8Fkciw6yyzrQ+R4Rsm6F6TH0pGzo545Yc7n7z5UNlA==} '@xrplf/isomorphic@1.0.1': resolution: {integrity: sha512-0bIpgx8PDjYdrLFeC3csF305QQ1L7sxaWnL5y71mCvhenZzJgku9QsA+9QCXBC1eNYtxWO/xR91zrXJy2T/ixg==} @@ -4632,14 +4565,6 @@ packages: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} @@ -4688,9 +4613,6 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - any-signal@3.0.1: - resolution: {integrity: sha512-xgZgJtKEa9YmDqXodIgl7Fl1C8yNXr8w6gXjqK3LW4GcEiYT+6AQfJSE/8SPsEpLLmcvbv8YU+qet94UewHxqg==} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4742,9 +4664,6 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - atomically@2.1.1: - resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} - attr-accept@2.2.5: resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} engines: {node: '>=4'} @@ -4872,10 +4791,6 @@ packages: big.js@6.2.2: resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} - bigint-mod-arith@3.3.1: - resolution: {integrity: sha512-pX/cYW3dCa87Jrzv6DAr8ivbbJRzEX5yGhdt8IutnX/PCIXfpx+mabWNK/M8qqh+zQ0J3thftUBHW0ByuUlG0w==} - engines: {node: '>=10.4.0'} - bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} @@ -4932,9 +4847,6 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browser-readablestream-to-it@1.0.3: - resolution: {integrity: sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw==} - browser-resolve@2.0.0: resolution: {integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==} @@ -5056,9 +4968,6 @@ packages: caniuse-lite@1.0.30001781: resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} - carstream@2.3.0: - resolution: {integrity: sha512-2YwFg5Kxs2tqVCJv7sthWbYoUpALCYBBfTdpQcpicV7ipi6bBb1h9M4MNb1vm+724f39lUNp5VWhW43IFxfPlA==} - cashaddrjs@0.4.4: resolution: {integrity: sha512-xZkuWdNOh0uq/mxJIng6vYWfTowZLd9F4GMAlp2DwFHlcCqCm91NtuAc47RuV4L7r4PYcY5p6Cr2OKNb4hnkWA==} @@ -5243,10 +5152,6 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} - conf@11.0.2: - resolution: {integrity: sha512-jjyhlQ0ew/iwmtwsS2RaB6s8DBifcE2GYBEaw2SJDUY/slJJbNfY4GlDVzOs/ff8cM/Wua5CikqXgbFl5eu85A==} - engines: {node: '>=14.16'} - confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -5372,10 +5277,6 @@ packages: dayjs@1.11.20: resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} - debounce-fn@5.1.2: - resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==} - engines: {node: '>=12'} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -5510,12 +5411,8 @@ packages: resolution: {integrity: sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==} engines: {node: '>=10'} - dot-prop@7.2.0: - resolution: {integrity: sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - dotenv@17.3.1: - resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + dotenv@17.4.1: + resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==} engines: {node: '>=12'} draggabilly@3.0.0: @@ -5633,10 +5530,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-fetch@1.9.1: - resolution: {integrity: sha512-M9qw6oUILGVrcENMSRRefE1MbHPIz0h79EKIeJWK9v563aT9Qkh8aEHPO1H5vi970wPirNY+jO9OpFoLiMsMGA==} - engines: {node: '>=6'} - electron-to-chromium@1.5.328: resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==} @@ -5705,17 +5598,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} - env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - err-code@3.0.1: - resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -5987,9 +5873,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -6209,9 +6092,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-iterator@1.0.2: - resolution: {integrity: sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==} - get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -6489,10 +6369,6 @@ packages: ipfs-unixfs@11.2.5: resolution: {integrity: sha512-uasYJ0GLPbViaTFsOLnL9YPjX5VmhnqtWRriogAHOe4ApmIi9VAOFBzgDHsUW2ub4pEa/EysbtWk126g2vkU/g==} - ipfs-utils@9.0.14: - resolution: {integrity: sha512-zIaiEGX18QATxgaS0/EOQNoo33W0islREABAcxXE8n7y2MGAlB+hdsxXn4J0hGZge8IqVQhW8sWIb+oJz2yEvg==} - engines: {node: '>=16.0.0', npm: '>=7.0.0'} - iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -6535,9 +6411,6 @@ packages: engines: {node: '>=8'} hasBin: true - is-electron@2.2.2: - resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -6635,10 +6508,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iso-url@1.2.1: - resolution: {integrity: sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==} - engines: {node: '>=12'} - isomorphic-timers-promises@1.0.1: resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==} engines: {node: '>=10'} @@ -6682,15 +6551,9 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - it-all@1.0.6: - resolution: {integrity: sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==} - it-filter@3.1.4: resolution: {integrity: sha512-80kWEKgiFEa4fEYD3mwf2uygo1dTQ5Y5midKtL89iXyjinruA/sNXl6iFkTcdNedydjvIsFhWLiqRPQP4fAwWQ==} - it-glob@1.0.2: - resolution: {integrity: sha512-Ch2Dzhw4URfB9L/0ZHyY+uqOnKvBNeS/SMcRiPmJfpHiM0TsUZn+GkpcZxAoF3dJVdPm/PuIk3A4wlV7SUo23Q==} - it-last@3.0.9: resolution: {integrity: sha512-AtfUEnGDBHBEwa1LjrpGHsJMzJAWDipD6zilvhakzJcm+BCvNX8zlX2BsHClHJLLTrsY4lY9JUjc+TQV4W7m1w==} @@ -6719,9 +6582,6 @@ packages: it-stream-types@2.0.2: resolution: {integrity: sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww==} - it-to-stream@1.0.0: - resolution: {integrity: sha512-pLULMZMAB/+vbdvbZtebC0nWBTbG581lk6w8P7DfIIIKUfa8FbY7Oi0FxZcFPbxvISs7A9E+cMpLDBc1XhpAOA==} - jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -6963,9 +6823,6 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -7357,10 +7214,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -7458,11 +7311,6 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true - native-fetch@3.0.0: - resolution: {integrity: sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==} - peerDependencies: - node-fetch: '*' - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -7598,9 +7446,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - one-webcrypto@1.0.3: - resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} - onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -7650,17 +7495,10 @@ packages: oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} - p-defer@3.0.0: - resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==} - engines: {node: '>=8'} - p-defer@4.0.1: resolution: {integrity: sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==} engines: {node: '>=12'} - p-fifo@1.0.0: - resolution: {integrity: sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A==} - p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -7689,10 +7527,6 @@ packages: resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} engines: {node: '>=18'} - p-retry@5.1.2: - resolution: {integrity: sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-timeout@6.1.4: resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} engines: {node: '>=14.16'} @@ -7824,6 +7658,18 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pinata@2.5.5: + resolution: {integrity: sha512-GmHGL8bSpiwQlbrXrT73DJ1ZrYi+MD3lY7scY10D2PnH0kggDCUP04Gb96thbRenhvqVYjPRYYFt7gW9yjejYw==} + engines: {node: '>=20'} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + pino-abstract-transport@0.5.0: resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} @@ -8107,9 +7953,6 @@ packages: react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19 react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19 - react-native-fetch-api@3.0.0: - resolution: {integrity: sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==} - react-native@0.83.1: resolution: {integrity: sha512-mL1q5HPq5cWseVhWRLl+Fwvi5z1UO+3vGOpjr+sHFwcUletPRZ5Kv+d0tUfqHmvi73/53NjlQqX1Pyn4GguUfA==} engines: {node: '>= 20.19.4'} @@ -8261,10 +8104,6 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -8616,9 +8455,6 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - stream-to-it@0.2.4: - resolution: {integrity: sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -8691,12 +8527,6 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - stubborn-fs@2.0.0: - resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} - - stubborn-utils@1.0.2: - resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} - style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -8745,9 +8575,6 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - sync-multihash-sha2@1.0.0: - resolution: {integrity: sha512-A5gVpmtKF0ov+/XID0M0QRJqF2QxAsj3x/LlDC8yivzgoYCoWkV+XaZPfVu7Vj1T/hYzYS1tfjwboSbXjqocug==} - synckit@0.11.12: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8981,10 +8808,6 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -9325,6 +9148,14 @@ packages: typescript: optional: true + viem@2.47.10: + resolution: {integrity: sha512-D+l6SDDZWB5bh8u9hgICzMX2/egMrgEQ+Pef/QkZgmOl6bOTyCQMSgWAH8jZTWJ/218J9QNv7s/9BH6Wu5oPDg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + viem@2.47.6: resolution: {integrity: sha512-zExmbI99NGvMdYa7fmqSTLgkwh48dmhgEqFrUgkpL4kfG4XkVefZ8dZqIKVUhZo6Uhf0FrrEXOsHm9LUyIvI2Q==} peerDependencies: @@ -9487,9 +9318,6 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - when-exit@2.1.5: - resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} - which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} @@ -10044,7 +9872,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.30.0(@types/node@25.5.0)': + '@changesets/cli@2.30.0(@types/node@25.5.2)': dependencies: '@changesets/apply-release-plan': 7.1.0 '@changesets/assemble-release-plan': 6.0.9 @@ -10060,7 +9888,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@25.5.0) + '@inquirer/external-editor': 1.0.3(@types/node@25.5.2) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -10158,19 +9986,19 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@coinbase/cdp-sdk@1.45.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@coinbase/cdp-sdk@1.45.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: - '@solana-program/system': 0.10.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@solana-program/token': 0.9.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)) - '@solana/kit': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana-program/system': 0.10.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)) + '@solana-program/token': 0.9.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)) + '@solana/kit': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) + '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6) abitype: 1.0.6(typescript@5.9.3)(zod@3.25.76) axios: 1.14.0 axios-retry: 4.5.0(axios@1.14.0) jose: 6.2.2 md5: 2.3.0 uncrypto: 0.1.3 - viem: 2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.45.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: - bufferutil @@ -10180,11 +10008,11 @@ snapshots: - typescript - utf-8-validate - '@coinbase/x402@2.1.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@coinbase/x402@2.1.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: - '@coinbase/cdp-sdk': 1.45.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@x402/core': 2.8.0 - viem: 2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@coinbase/cdp-sdk': 1.45.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) + '@x402/core': 2.9.0 + viem: 2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: - bufferutil @@ -10196,12 +10024,12 @@ snapshots: '@coral-xyz/anchor-errors@0.31.1': {} - '@coral-xyz/anchor@0.31.1(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@coral-xyz/anchor@0.31.1(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@coral-xyz/anchor-errors': 0.31.1 - '@coral-xyz/borsh': 0.31.1(@solana/web3.js@1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@coral-xyz/borsh': 0.31.1(@solana/web3.js@1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6)) '@noble/hashes': 1.8.0 - '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6) bn.js: 5.2.3 bs58: 4.0.1 buffer-layout: 1.2.2 @@ -10217,9 +10045,9 @@ snapshots: - typescript - utf-8-validate - '@coral-xyz/borsh@0.31.1(@solana/web3.js@1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10))': + '@coral-xyz/borsh@0.31.1(@solana/web3.js@1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6))': dependencies: - '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@6.0.6) bn.js: 5.2.3 buffer-layout: 1.2.2 @@ -10703,12 +10531,12 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/external-editor@1.0.3(@types/node@25.5.0)': + '@inquirer/external-editor@1.0.3(@types/node@25.5.2)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@ipld/car@5.4.2': dependencies: @@ -10731,12 +10559,6 @@ snapshots: dependencies: multiformats: 13.4.2 - '@ipld/dag-ucan@3.4.5': - dependencies: - '@ipld/dag-cbor': 9.2.5 - '@ipld/dag-json': 10.2.6 - multiformats: 13.4.2 - '@ipld/unixfs@3.0.0': dependencies: '@ipld/dag-pb': 4.1.5 @@ -10822,7 +10644,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.15 + '@types/node': 25.5.2 jest-mock: 29.7.0 '@jest/environment@30.3.0': @@ -10847,7 +10669,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.19.15 + '@types/node': 25.5.2 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -11215,8 +11037,6 @@ snapshots: dependencies: '@noble/hashes': 2.0.1 - '@noble/ed25519@1.7.5': {} - '@noble/hashes@1.3.2': {} '@noble/hashes@1.4.0': {} @@ -12317,9 +12137,9 @@ snapshots: dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - '@solana-program/system@0.10.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10))': + '@solana-program/system@0.10.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6))': dependencies: - '@solana/kit': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/kit': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) '@solana-program/system@0.7.0(@solana/kit@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))': dependencies: @@ -12334,9 +12154,9 @@ snapshots: dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - '@solana-program/token@0.9.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10))': + '@solana-program/token@0.9.0(@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6))': dependencies: - '@solana/kit': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/kit': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) '@solana/accounts@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': dependencies: @@ -12633,7 +12453,7 @@ snapshots: - fastestsmallesttextencoderdecoder - ws - '@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@solana/kit@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@solana/accounts': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) @@ -12650,11 +12470,11 @@ snapshots: '@solana/rpc-api': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/rpc-parsed-types': 5.5.1(typescript@5.9.3) '@solana/rpc-spec-types': 5.5.1(typescript@5.9.3) - '@solana/rpc-subscriptions': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/rpc-subscriptions': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/signers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/sysvars': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) - '@solana/transaction-confirmation': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/transaction-confirmation': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) optionalDependencies: @@ -12848,13 +12668,13 @@ snapshots: typescript: 5.9.3 ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@solana/rpc-subscriptions-channel-websocket@5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@solana/rpc-subscriptions-channel-websocket@5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@solana/errors': 5.5.1(typescript@5.9.3) '@solana/functional': 5.5.1(typescript@5.9.3) '@solana/rpc-subscriptions-spec': 5.5.1(typescript@5.9.3) '@solana/subscribable': 5.5.1(typescript@5.9.3) - ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -12914,7 +12734,7 @@ snapshots: - fastestsmallesttextencoderdecoder - ws - '@solana/rpc-subscriptions@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@solana/rpc-subscriptions@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@solana/errors': 5.5.1(typescript@5.9.3) '@solana/fast-stable-stringify': 5.5.1(typescript@5.9.3) @@ -12922,7 +12742,7 @@ snapshots: '@solana/promises': 5.5.1(typescript@5.9.3) '@solana/rpc-spec-types': 5.5.1(typescript@5.9.3) '@solana/rpc-subscriptions-api': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) - '@solana/rpc-subscriptions-channel-websocket': 5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/rpc-subscriptions-channel-websocket': 5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@solana/rpc-subscriptions-spec': 5.5.1(typescript@5.9.3) '@solana/rpc-transformers': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) @@ -13126,7 +12946,7 @@ snapshots: - fastestsmallesttextencoderdecoder - ws - '@solana/transaction-confirmation@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@solana/transaction-confirmation@5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@solana/addresses': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/codecs-strings': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) @@ -13134,7 +12954,7 @@ snapshots: '@solana/keys': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/promises': 5.5.1(typescript@5.9.3) '@solana/rpc': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) - '@solana/rpc-subscriptions': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/rpc-subscriptions': 5.5.1(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@6.0.6) '@solana/rpc-types': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/transaction-messages': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/transactions': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) @@ -13786,101 +13606,6 @@ snapshots: transitivePeerDependencies: - debug - '@storacha/access@1.6.9': - dependencies: - '@ipld/car': 5.4.2 - '@ipld/dag-ucan': 3.4.5 - '@scure/bip39': 1.6.0 - '@storacha/capabilities': 2.3.1 - '@storacha/did-mailto': 1.0.2 - '@storacha/one-webcrypto': 1.0.1 - '@ucanto/client': 9.0.2 - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - '@ucanto/principal': 9.0.3 - '@ucanto/transport': 9.2.1 - '@ucanto/validator': 10.0.1 - bigint-mod-arith: 3.3.1 - conf: 11.0.2 - multiformats: 13.4.2 - p-defer: 4.0.1 - type-fest: 4.41.0 - uint8arrays: 5.1.0 - - '@storacha/blob-index@1.2.9': - dependencies: - '@ipld/dag-cbor': 9.2.5 - '@storacha/capabilities': 2.3.1 - '@storacha/one-webcrypto': 1.0.1 - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - carstream: 2.3.0 - multiformats: 13.4.2 - sade: 1.8.1 - uint8arrays: 5.1.0 - - '@storacha/capabilities@2.3.1': - dependencies: - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - '@ucanto/principal': 9.0.3 - '@ucanto/transport': 9.2.1 - '@ucanto/validator': 10.0.1 - '@web3-storage/data-segment': 5.3.0 - multiformats: 13.4.2 - - '@storacha/client@2.1.3(encoding@0.1.13)': - dependencies: - '@ipld/dag-ucan': 3.4.5 - '@storacha/access': 1.6.9 - '@storacha/blob-index': 1.2.9 - '@storacha/capabilities': 2.3.1 - '@storacha/did-mailto': 1.0.2 - '@storacha/filecoin-client': 1.0.20 - '@storacha/upload-client': 1.4.2(encoding@0.1.13) - '@ucanto/client': 9.0.2 - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - '@ucanto/principal': 9.0.3 - '@ucanto/transport': 9.2.1 - environment: 1.1.0 - transitivePeerDependencies: - - encoding - - '@storacha/did-mailto@1.0.2': {} - - '@storacha/filecoin-client@1.0.20': - dependencies: - '@ipld/dag-ucan': 3.4.5 - '@storacha/capabilities': 2.3.1 - '@ucanto/client': 9.0.2 - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - '@ucanto/transport': 9.2.1 - - '@storacha/one-webcrypto@1.0.1': {} - - '@storacha/upload-client@1.4.2(encoding@0.1.13)': - dependencies: - '@ipld/car': 5.4.2 - '@ipld/dag-cbor': 9.2.5 - '@ipld/dag-ucan': 3.4.5 - '@ipld/unixfs': 3.0.0 - '@storacha/blob-index': 1.2.9 - '@storacha/capabilities': 2.3.1 - '@storacha/filecoin-client': 1.0.20 - '@ucanto/client': 9.0.2 - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - '@ucanto/transport': 9.2.1 - '@web3-storage/data-segment': 5.3.0 - ipfs-utils: 9.0.14(encoding@0.1.13) - multiformats: 13.4.2 - p-retry: 5.1.2 - varint: 6.0.0 - transitivePeerDependencies: - - encoding - '@stylistic/eslint-plugin@5.10.0(eslint@9.39.4(jiti@2.6.1))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) @@ -14088,14 +13813,14 @@ snapshots: '@tanstack/virtual-file-routes@1.161.7': {} - '@toju.network/fil@0.2.2(bufferutil@4.1.0)(utf-8-validate@5.0.10)': + '@toju.network/fil@1.0.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)': dependencies: ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - '@toju.network/sol@0.1.6(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + '@toju.network/sol@1.0.0(bufferutil@4.1.0)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(utf-8-validate@5.0.10)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': dependencies: '@solana/kit': 2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -14592,8 +14317,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/minimatch@3.0.5': {} - '@types/morgan@1.9.10': dependencies: '@types/node': 24.12.0 @@ -14622,7 +14345,7 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.5.0': + '@types/node@25.5.2': dependencies: undici-types: 7.18.2 @@ -14656,8 +14379,6 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/retry@0.12.1': {} - '@types/send@1.2.1': dependencies: '@types/node': 24.12.0 @@ -14691,11 +14412,11 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 12.20.55 + '@types/node': 24.12.0 '@types/ws@8.18.1': dependencies: - '@types/node': 25.5.0 + '@types/node': 24.12.0 '@types/yargs-parser@21.0.3': {} @@ -14794,47 +14515,6 @@ snapshots: '@typescript-eslint/types': 8.57.2 eslint-visitor-keys: 5.0.1 - '@ucanto/client@9.0.2': - dependencies: - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - - '@ucanto/core@10.4.6': - dependencies: - '@ipld/car': 5.4.2 - '@ipld/dag-cbor': 9.2.5 - '@ipld/dag-ucan': 3.4.5 - '@ucanto/interface': 11.0.1 - multiformats: 13.4.2 - - '@ucanto/interface@11.0.1': - dependencies: - '@ipld/dag-ucan': 3.4.5 - multiformats: 13.4.2 - - '@ucanto/principal@9.0.3': - dependencies: - '@ipld/dag-ucan': 3.4.5 - '@noble/curves': 1.9.7 - '@noble/ed25519': 1.7.5 - '@noble/hashes': 1.8.0 - '@ucanto/interface': 11.0.1 - multiformats: 13.4.2 - one-webcrypto: 1.0.3 - - '@ucanto/transport@9.2.1': - dependencies: - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - - '@ucanto/validator@10.0.1': - dependencies: - '@ipld/car': 5.4.2 - '@ipld/dag-cbor': 9.2.5 - '@ucanto/core': 10.4.6 - '@ucanto/interface': 11.0.1 - multiformats: 13.4.2 - '@ungap/structured-clone@1.3.0': {} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -15549,29 +15229,13 @@ snapshots: multiformats: 13.4.2 uint8arrays: 5.1.0 - '@web3-storage/data-segment@5.3.0': - dependencies: - '@ipld/dag-cbor': 9.2.5 - multiformats: 13.4.2 - sync-multihash-sha2: 1.0.0 - - '@x402/core@2.8.0': + '@x402/core@2.9.0': dependencies: zod: 3.25.76 - '@x402/evm@2.8.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@x402/evm@2.9.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: - '@x402/core': 2.8.0 - viem: 2.45.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) - zod: 3.25.76 - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - '@x402/evm@2.8.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': - dependencies: - '@x402/core': 2.8.0 + '@x402/core': 2.9.0 viem: 2.45.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: @@ -15579,9 +15243,9 @@ snapshots: - typescript - utf-8-validate - '@x402/evm@2.8.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)': + '@x402/evm@2.9.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)': dependencies: - '@x402/core': 2.8.0 + '@x402/core': 2.9.0 viem: 2.45.3(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: @@ -15589,12 +15253,12 @@ snapshots: - typescript - utf-8-validate - '@x402/express@2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(express@5.2.1)(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@x402/express@2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(express@5.2.1)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: - '@x402/core': 2.8.0 - '@x402/extensions': 2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@5.9.3)(utf-8-validate@5.0.10) + '@x402/core': 2.9.0 + '@x402/extensions': 2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(typescript@5.9.3)(utf-8-validate@6.0.6) express: 5.2.1 - viem: 2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: - bufferutil @@ -15602,10 +15266,10 @@ snapshots: - typescript - utf-8-validate - '@x402/express@2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(express@5.2.1)(typescript@6.0.2)(utf-8-validate@6.0.6)': + '@x402/express@2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(express@5.2.1)(typescript@6.0.2)(utf-8-validate@6.0.6)': dependencies: - '@x402/core': 2.8.0 - '@x402/extensions': 2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(typescript@6.0.2)(utf-8-validate@6.0.6) + '@x402/core': 2.9.0 + '@x402/extensions': 2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(typescript@6.0.2)(utf-8-validate@6.0.6) express: 5.2.1 viem: 2.47.6(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 @@ -15615,16 +15279,16 @@ snapshots: - typescript - utf-8-validate - '@x402/extensions@2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(typescript@5.9.3)(utf-8-validate@5.0.10)': + '@x402/extensions@2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: '@noble/curves': 1.9.7 '@scure/base': 1.2.6 - '@x402/core': 2.8.0 + '@x402/core': 2.9.0 ajv: 8.18.0 jose: 5.10.0 - siwe: 2.3.2(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + siwe: 2.3.2(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) tweetnacl: 1.0.3 - viem: 2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + viem: 2.45.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: - bufferutil @@ -15632,16 +15296,16 @@ snapshots: - typescript - utf-8-validate - '@x402/extensions@2.8.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(typescript@6.0.2)(utf-8-validate@6.0.6)': + '@x402/extensions@2.9.0(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6))(typescript@6.0.2)(utf-8-validate@6.0.6)': dependencies: '@noble/curves': 1.9.7 '@scure/base': 1.2.6 - '@x402/core': 2.8.0 + '@x402/core': 2.9.0 ajv: 8.18.0 jose: 5.10.0 siwe: 2.3.2(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) tweetnacl: 1.0.3 - viem: 2.47.6(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76) + viem: 2.45.3(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: - bufferutil @@ -15649,9 +15313,9 @@ snapshots: - typescript - utf-8-validate - '@x402/fetch@2.8.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': + '@x402/fetch@2.9.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: - '@x402/core': 2.8.0 + '@x402/core': 2.9.0 viem: 2.45.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: @@ -15659,9 +15323,9 @@ snapshots: - typescript - utf-8-validate - '@x402/fetch@2.8.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)': + '@x402/fetch@2.9.0(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)': dependencies: - '@x402/core': 2.8.0 + '@x402/core': 2.9.0 viem: 2.45.3(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76) zod: 3.25.76 transitivePeerDependencies: @@ -15765,10 +15429,6 @@ snapshots: dependencies: humanize-ms: 1.2.1 - ajv-formats@2.1.1(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -15811,8 +15471,6 @@ snapshots: any-promise@1.3.0: {} - any-signal@3.0.1: {} - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -15864,11 +15522,6 @@ snapshots: atomic-sleep@1.0.0: {} - atomically@2.1.1: - dependencies: - stubborn-fs: 2.0.0 - when-exit: 2.1.5 - attr-accept@2.2.5: {} available-typed-arrays@1.0.7: @@ -16035,8 +15688,6 @@ snapshots: big.js@6.2.2: {} - bigint-mod-arith@3.3.1: {} - bignumber.js@9.3.1: {} binary-extensions@2.3.0: {} @@ -16102,8 +15753,6 @@ snapshots: brorand@1.1.0: {} - browser-readablestream-to-it@1.0.3: {} - browser-resolve@2.0.0: dependencies: resolve: 1.22.11 @@ -16254,12 +15903,6 @@ snapshots: caniuse-lite@1.0.30001781: {} - carstream@2.3.0: - dependencies: - '@ipld/dag-cbor': 9.2.5 - multiformats: 13.4.2 - uint8arraylist: 2.4.8 - cashaddrjs@0.4.4: dependencies: big-integer: 1.6.36 @@ -16327,7 +15970,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -16336,7 +15979,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -16437,17 +16080,6 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - conf@11.0.2: - dependencies: - ajv: 8.18.0 - ajv-formats: 2.1.1(ajv@8.18.0) - atomically: 2.1.1 - debounce-fn: 5.1.2 - dot-prop: 7.2.0 - env-paths: 3.0.0 - json-schema-typed: 8.0.2 - semver: 7.7.4 - confbox@0.1.8: {} connect@3.7.0: @@ -16608,10 +16240,6 @@ snapshots: dayjs@1.11.20: {} - debounce-fn@5.1.2: - dependencies: - mimic-fn: 4.0.0 - debug@2.6.9: dependencies: ms: 2.0.0 @@ -16709,11 +16337,7 @@ snapshots: domain-browser@4.22.0: {} - dot-prop@7.2.0: - dependencies: - type-fest: 2.19.0 - - dotenv@17.3.1: {} + dotenv@17.4.1: {} draggabilly@3.0.0: dependencies: @@ -16756,10 +16380,6 @@ snapshots: ee-first@1.1.1: {} - electron-fetch@1.9.1: - dependencies: - encoding: 0.1.13 - electron-to-chromium@1.5.328: {} elliptic@6.6.1: @@ -16801,6 +16421,7 @@ snapshots: encoding@0.1.13: dependencies: iconv-lite: 0.6.3 + optional: true end-of-stream@1.4.5: dependencies: @@ -16832,12 +16453,8 @@ snapshots: entities@6.0.1: {} - env-paths@3.0.0: {} - environment@1.1.0: {} - err-code@3.0.1: {} - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -17254,8 +16871,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-fifo@1.3.2: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -17470,8 +17085,6 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-iterator@1.0.2: {} - get-nonce@1.0.1: {} get-package-type@0.1.0: {} @@ -17696,6 +17309,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true iconv-lite@0.7.2: dependencies: @@ -17807,27 +17421,6 @@ snapshots: protons-runtime: 5.6.0 uint8arraylist: 2.4.8 - ipfs-utils@9.0.14(encoding@0.1.13): - dependencies: - any-signal: 3.0.1 - browser-readablestream-to-it: 1.0.3 - buffer: 6.0.3 - electron-fetch: 1.9.1 - err-code: 3.0.1 - is-electron: 2.2.2 - iso-url: 1.2.1 - it-all: 1.0.6 - it-glob: 1.0.2 - it-to-stream: 1.0.0 - merge-options: 3.0.4 - nanoid: 3.3.11 - native-fetch: 3.0.0(node-fetch@2.7.0(encoding@0.1.13)) - node-fetch: 2.7.0(encoding@0.1.13) - react-native-fetch-api: 3.0.0 - stream-to-it: 0.2.4 - transitivePeerDependencies: - - encoding - iron-webcrypto@1.2.1: {} is-alphabetical@2.0.1: {} @@ -17862,8 +17455,6 @@ snapshots: is-docker@2.2.1: {} - is-electron@2.2.2: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -17895,7 +17486,8 @@ snapshots: is-number@7.0.0: {} - is-plain-obj@2.1.0: {} + is-plain-obj@2.1.0: + optional: true is-plain-obj@4.1.0: {} @@ -17940,8 +17532,6 @@ snapshots: isexe@2.0.0: {} - iso-url@1.2.1: {} - isomorphic-timers-promises@1.0.1: {} isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10)): @@ -18005,17 +17595,10 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - it-all@1.0.6: {} - it-filter@3.1.4: dependencies: it-peekable: 3.0.8 - it-glob@1.0.2: - dependencies: - '@types/minimatch': 3.0.5 - minimatch: 3.1.5 - it-last@3.0.9: {} it-map@3.1.4: @@ -18050,15 +17633,6 @@ snapshots: it-stream-types@2.0.2: {} - it-to-stream@1.0.0: - dependencies: - buffer: 6.0.3 - fast-fifo: 1.3.2 - get-iterator: 1.0.2 - p-defer: 3.0.0 - p-fifo: 1.0.0 - readable-stream: 3.6.2 - jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -18208,7 +17782,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.15 + '@types/node': 25.5.2 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -18294,7 +17868,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.19.15 + '@types/node': 25.5.2 jest-util: 29.7.0 jest-mock@30.3.0: @@ -18553,8 +18127,6 @@ snapshots: json-schema-traverse@1.0.0: {} - json-schema-typed@8.0.2: {} - json-stable-stringify-without-jsonify@1.0.1: {} json-stable-stringify@1.3.0: @@ -18638,10 +18210,10 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - knip@5.88.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(typescript@5.9.3): + knip@5.88.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 25.5.0 + '@types/node': 25.5.2 fast-glob: 3.3.3 formatly: 0.3.0 jiti: 2.6.1 @@ -18904,6 +18476,7 @@ snapshots: merge-options@3.0.4: dependencies: is-plain-obj: 2.1.0 + optional: true merge-stream@2.0.0: {} @@ -19242,8 +18815,6 @@ snapshots: mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} - mimic-function@5.0.1: {} minimalistic-assert@1.0.1: {} @@ -19328,10 +18899,6 @@ snapshots: napi-postinstall@0.3.4: {} - native-fetch@3.0.0(node-fetch@2.7.0(encoding@0.1.13)): - dependencies: - node-fetch: 2.7.0(encoding@0.1.13) - natural-compare@1.4.0: {} negotiator@1.0.0: {} @@ -19476,8 +19043,6 @@ snapshots: dependencies: wrappy: 1.0.2 - one-webcrypto@1.0.3: {} - onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -19612,8 +19177,8 @@ snapshots: ox@0.6.7(typescript@5.9.3)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.1 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 '@scure/bip32': 1.6.2 '@scure/bip39': 1.5.4 abitype: 1.0.8(typescript@5.9.3)(zod@3.25.76) @@ -19649,15 +19214,8 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - p-defer@3.0.0: {} - p-defer@4.0.1: {} - p-fifo@1.0.0: - dependencies: - fast-fifo: 1.3.2 - p-defer: 3.0.0 - p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -19685,11 +19243,6 @@ snapshots: eventemitter3: 5.0.4 p-timeout: 6.1.4 - p-retry@5.1.2: - dependencies: - '@types/retry': 0.12.1 - retry: 0.13.1 - p-timeout@6.1.4: {} p-try@2.2.0: {} @@ -19814,6 +19367,11 @@ snapshots: pify@4.0.1: {} + pinata@2.5.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + pino-abstract-transport@0.5.0: dependencies: duplexify: 4.1.3 @@ -19934,7 +19492,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.19.15 + '@types/node': 24.12.0 long: 5.2.5 protobufjs@7.5.4: @@ -20134,10 +19692,6 @@ snapshots: react-lifecycles-compat: 3.0.4 warning: 4.0.3 - react-native-fetch-api@3.0.0: - dependencies: - p-defer: 3.0.0 - react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.4)(utf-8-validate@5.0.10): dependencies: '@jest/create-cache-key-function': 29.7.0 @@ -20334,8 +19888,6 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - retry@0.13.1: {} - reusify@1.1.0: {} rfdc@1.4.1: {} @@ -20626,14 +20178,6 @@ snapshots: dependencies: semver: 7.7.4 - siwe@2.3.2(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)): - dependencies: - '@spruceid/siwe-parser': 2.1.2 - '@stablelib/random': 1.0.2 - ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - uri-js: 4.4.1 - valid-url: 1.0.9 - siwe@2.3.2(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)): dependencies: '@spruceid/siwe-parser': 2.1.2 @@ -20784,10 +20328,6 @@ snapshots: stream-shift@1.0.3: {} - stream-to-it@0.2.4: - dependencies: - get-iterator: 1.0.2 - streamsearch@1.1.0: {} strict-uri-encode@2.0.0: {} @@ -20857,12 +20397,6 @@ snapshots: dependencies: js-tokens: 9.0.1 - stubborn-fs@2.0.0: - dependencies: - stubborn-utils: 1.0.2 - - stubborn-utils@1.0.2: {} - style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -20914,10 +20448,6 @@ snapshots: symbol-tree@3.2.4: {} - sync-multihash-sha2@1.0.0: - dependencies: - '@noble/hashes': 1.8.0 - synckit@0.11.12: dependencies: '@pkgr/core': 0.2.9 @@ -21121,8 +20651,6 @@ snapshots: type-fest@0.7.1: {} - type-fest@2.19.0: {} - type-fest@4.41.0: {} type-is@1.6.18: @@ -21518,49 +21046,49 @@ snapshots: - utf-8-validate - zod - viem@2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): + viem@2.47.10(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@4.3.6): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) - isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - ox: 0.14.7(typescript@5.9.3)(zod@3.25.76) - ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + abitype: 1.2.3(typescript@6.0.2)(zod@4.3.6) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + ox: 0.14.7(typescript@6.0.2)(zod@4.3.6) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.47.6(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76): + viem@2.47.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@6.0.2)(zod@3.25.76) + abitype: 1.2.3(typescript@5.9.3)(zod@3.25.76) isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - ox: 0.14.7(typescript@6.0.2)(zod@3.25.76) + ox: 0.14.7(typescript@5.9.3)(zod@3.25.76) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - typescript: 6.0.2 + typescript: 5.9.3 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.47.6(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@4.3.6): + viem@2.47.6(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@6.0.6)(zod@3.25.76): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.3(typescript@6.0.2)(zod@3.25.76) isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - ox: 0.14.7(typescript@6.0.2)(zod@4.3.6) + ox: 0.14.7(typescript@6.0.2)(zod@3.25.76) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: typescript: 6.0.2 @@ -21742,8 +21270,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - when-exit@2.1.5: {} - which-module@2.0.1: {} which-typed-array@1.1.20: diff --git a/server/package.json b/server/package.json index 3f94909..aefb633 100644 --- a/server/package.json +++ b/server/package.json @@ -43,14 +43,11 @@ "@sentry/node": "^10.46.0", "@sentry/profiling-node": "^10.46.0", "@solana/web3.js": "^1.98.4", - "@storacha/client": "^2.1.3", "@types/bn.js": "^5.2.0", - "@ucanto/core": "^10.4.6", "@upstash/qstash": "^2.10.1", "@x402/core": "^2.8.0", "@x402/evm": "^2.8.0", "@x402/express": "^2.8.0", - "axios": "^1.14.0", "bn.js": "^5.2.3", "cors": "^2.8.6", "dotenv": "^17.3.1", @@ -63,6 +60,7 @@ "multer": "^2.1.1", "node-fetch": "^3.3.2", "pg": "^8.20.0", + "pinata": "2.5.5", "postgres": "^3.4.8", "resend": "^6.9.4", "ts-jest": "^29.4.6", diff --git a/server/src/controllers/agent.controller.ts b/server/src/controllers/agent.controller.ts index d99ebdb..da9a379 100644 --- a/server/src/controllers/agent.controller.ts +++ b/server/src/controllers/agent.controller.ts @@ -3,18 +3,16 @@ import { eq } from 'drizzle-orm' import { Request, Response } from 'express' import { db } from '../db/db.js' import { uploads } from '../db/schema.js' +import { pinFiles } from '../services/storage/pinata.service.js' import { computeCID } from '../utils/compute-cid.js' import { getAmountInUSD } from '../utils/constant.js' import { getExpiryDate } from '../utils/functions.js' import { logger } from '../utils/logger.js' -import { getPricingConfig, initStorachaClient } from '../utils/storacha.js' - -const SHARD_SIZE = 10_485_760 +import { getPricingConfig } from '../utils/pricing.js' // we should likely consider how renewal would work for agents, in the future. // for now, we can bank on the idea that agents may store for long-running course -// or epochs, say 2 years or mor. regardless, we still need to factor it. -// but for now, i'll rest. +// or epochs, say 2 years or more. regardless, we still need to factor it. /** * Agent file upload — runs after x402 payment middleware has verified the USDC payment. * @@ -48,6 +46,7 @@ export const uploadAgentFile = async (req: Request, res: Response) => { } const computedCID = await computeCID(fileMap) + const existing = await db .select() .from(uploads) @@ -61,19 +60,21 @@ export const uploadAgentFile = async (req: Request, res: Response) => { expiresAt: existing[0].expiresAt, }) - const client = await initStorachaClient() - const fileObject = new File([file.buffer], file.originalname, { - type: file.mimetype, - }) - const uploadedCID = await client.uploadFile(fileObject, { - shardSize: SHARD_SIZE, - }) + const pinnedCID = await pinFiles( + { + [file.originalname]: { + buffer: new Uint8Array(file.buffer), + mimetype: file.mimetype, + }, + }, + file.originalname, + ) - if (uploadedCID.toString() !== computedCID) { - throw new Error( - `CID mismatch: computed=${computedCID}, uploaded=${uploadedCID}`, - ) - } + if (pinnedCID !== computedCID) + logger.warn('CID mismatch between pre-computed and pinned', { + computed: computedCID, + pinned: pinnedCID, + }) let payerAddress = 'agent' const xPayment = req.headers['x-payment'] as string | undefined @@ -82,7 +83,7 @@ export const uploadAgentFile = async (req: Request, res: Response) => { const decoded = JSON.parse(Buffer.from(xPayment, 'base64').toString()) payerAddress = decoded?.payload?.authorization?.from || 'agent' } catch { - // should leave it as 'agent'?? boya! + // leave it as 'agent' } } @@ -92,16 +93,13 @@ export const uploadAgentFile = async (req: Request, res: Response) => { // on-chain settlement happens asynchronously via Coinbase facilitator const transactionHash = `x402:base:${Date.now()}:${computedCID.slice(0, 12)}` - // USDC has 6 decimals compared to other ERC-20 tokens that uses 18 decimals. - // we should store amount in micro-USDC (same unit as USDC atomic units). - // apply the same $0.000001 floor the x402 middleware uses so we don't get - // a disparity between what the agent is charged and what we store in our db. + // USDC has 6 decimals. store amount in micro-USDC (same unit as USDC atomic units). + // apply the same $0.000001 floor the x402 middleware uses. const { ratePerBytePerDay } = await getPricingConfig() const costUSD = Math.max( getAmountInUSD(size, ratePerBytePerDay, duration), 0.000001, ) - // micro-USDC (6 decimals), e.g. 90000 = $0.09 const depositAmount = Math.ceil(costUSD * 1_000_000) const depositItem: typeof uploads.$inferInsert = { diff --git a/server/src/controllers/console.controller.ts b/server/src/controllers/console.controller.ts index cb53da0..680a052 100644 --- a/server/src/controllers/console.controller.ts +++ b/server/src/controllers/console.controller.ts @@ -2,7 +2,6 @@ import { Request, Response } from 'express' import { UsageService } from '../services/usage/usage.service.js' import { logger } from '../utils/logger.js' import { getEscrowBalance, withdrawFees } from '../utils/solana/index.js' -import { initStorachaClient } from '../utils/storacha.js' /** * get usage history @@ -10,12 +9,8 @@ import { initStorachaClient } from '../utils/storacha.js' export const getUsageHistory = async (req: Request, res: Response) => { try { const days = parseInt(req.query.days as string, 10) || 30 - - const storachaClient = await initStorachaClient() - const usageService = new UsageService(storachaClient) - await usageService.initialize() + const usageService = new UsageService() const history = await usageService.getUsageHistory(days) - return res.status(200).json({ success: true, data: history, @@ -34,24 +29,23 @@ export const getUsageHistory = async (req: Request, res: Response) => { */ export const getCurrentUsage = async (_req: Request, res: Response) => { try { - const storachaClient = await initStorachaClient() - const usageService = new UsageService(storachaClient) - await usageService.initialize() + const usageService = new UsageService() - const [storachaUsage, internalUsage] = await Promise.all([ - usageService.getStorachaUsage(), + const [pinataUsage, internalUsage] = await Promise.all([ + usageService.getUsage(), usageService.calculateInternalUsage(), ]) - const storachaBytes = storachaUsage.finalSize const planLimit = usageService.planLimit - const utilization = planLimit > 0 ? (storachaBytes / planLimit) * 100 : 0 + const utilization = + planLimit > 0 ? (pinataUsage.totalSizeBytes / planLimit) * 100 : 0 return res.status(200).json({ success: true, data: { - storacha: { - totalBytes: storachaBytes, + pinata: { + totalBytes: pinataUsage.totalSizeBytes, + pinCount: pinataUsage.pinCount, planLimit, utilizationPercentage: utilization, }, @@ -60,10 +54,10 @@ export const getCurrentUsage = async (_req: Request, res: Response) => { activeUploads: internalUsage.activeUploads, }, discrepancy: { - bytes: storachaBytes - internalUsage.totalBytes, + bytes: pinataUsage.totalSizeBytes - internalUsage.totalBytes, percentage: internalUsage.totalBytes > 0 - ? ((storachaBytes - internalUsage.totalBytes) / + ? ((pinataUsage.totalSizeBytes - internalUsage.totalBytes) / internalUsage.totalBytes) * 100 : 0, @@ -84,11 +78,8 @@ export const getCurrentUsage = async (_req: Request, res: Response) => { */ export const getUnresolvedAlerts = async (_req: Request, res: Response) => { try { - const storachaClient = await initStorachaClient() - const usageService = new UsageService(storachaClient) - await usageService.initialize() + const usageService = new UsageService() const alerts = await usageService.getUnresolvedAlerts() - return res.status(200).json({ success: true, data: alerts, @@ -111,12 +102,8 @@ export const resolveAlert = async (req: Request, res: Response) => { Array.isArray(req.params.id) ? req.params.id[0] : req.params.id, 10, ) - - const storachaClient = await initStorachaClient() - const usageService = new UsageService(storachaClient) - await usageService.initialize() + const usageService = new UsageService() await usageService.resolveAlert(alertId) - return res.status(200).json({ success: true, message: 'alert resolved', @@ -136,8 +123,6 @@ export const resolveAlert = async (req: Request, res: Response) => { export const getEscrowVaultBalance = async (_req: Request, res: Response) => { try { const balance = await getEscrowBalance() - - // convert bigint to string for JSON serialization return res.status(200).json({ success: true, data: { @@ -145,7 +130,6 @@ export const getEscrowVaultBalance = async (_req: Request, res: Response) => { totalClaimed: balance.totalClaimed.toString(), availableBalance: balance.availableBalance.toString(), accountLamports: balance.accountLamports.toString(), - availableBalanceSOL: Number(balance.availableBalance) / 1e9, totalDepositsSOL: Number(balance.totalDeposits) / 1e9, totalClaimedSOL: Number(balance.totalClaimed) / 1e9, diff --git a/server/src/controllers/jobs.controller.ts b/server/src/controllers/jobs.controller.ts index 83e3d54..842dfd1 100644 --- a/server/src/controllers/jobs.controller.ts +++ b/server/src/controllers/jobs.controller.ts @@ -1,16 +1,18 @@ -import { UnknownLink } from '@storacha/client/types' -import { Link } from '@ucanto/core/schema' +import { eq } from 'drizzle-orm' import { Request, Response } from 'express' +import { db } from '../db/db.js' +import { uploads } from '../db/schema.js' import { batchUpdateWarningSentAt, + getAbandonedPendingUploads, getExpiredDeposits, getUploadsGroupedByEmail, updateDeletionStatus, -} from '../db/uploads-table.js' +} from '../db/uploads.js' import { sendBatchExpirationWarningEmail } from '../services/email/resend.service.js' +import { unpinCID } from '../services/storage/pinata.service.js' import { UsageService } from '../services/usage/usage.service.js' import { logger } from '../utils/logger.js' -import { initStorachaClient } from '../utils/storacha.js' /** * Checks for uploads needing expiration warnings and sends batched emails @@ -103,7 +105,7 @@ export const sendExpirationWarnings = async (_req: Request, res: Response) => { } /** - * Deletes expired deposits from Storacha + * Deletes expired uploads from Pinata and marks them as deleted in the DB */ export const deleteExpiredUploads = async (_req: Request, res: Response) => { try { @@ -121,7 +123,6 @@ export const deleteExpiredUploads = async (_req: Request, res: Response) => { logger.info('Found expired uploads to delete', { count: expiredDeposits.length, }) - const client = await initStorachaClient() for (const deposit of expiredDeposits) { try { @@ -133,10 +134,7 @@ export const deleteExpiredUploads = async (_req: Request, res: Response) => { status: deposit.deletionStatus, }) - const cid = Link.parse(deposit.contentCid) - await client.remove(cid as UnknownLink, { - shards: true, - }) + await unpinCID(deposit.contentCid) await updateDeletionStatus(deposit.id, 'deleted') logger.info('Successfully deleted this upload', { depositId: deposit.id, @@ -167,6 +165,56 @@ export const deleteExpiredUploads = async (_req: Request, res: Response) => { } } +/** + * Unpins and removes pending uploads that were never paid for. + * Runs daily — cleans up files pinned during deposit but abandoned before payment. + */ +export const deleteAbandonedUploads = async (_req: Request, res: Response) => { + try { + logger.info('Running abandoned uploads cleanup job') + const abandoned = await getAbandonedPendingUploads(24) + + if (!abandoned || abandoned.length === 0) { + logger.info('No abandoned uploads to clean up') + return res + .status(200) + .json({ success: true, message: 'No abandoned uploads to clean up' }) + } + + logger.info('Found abandoned uploads to clean up', { + count: abandoned.length, + }) + + for (const record of abandoned) { + try { + await unpinCID(record.contentCid) + await db.delete(uploads).where(eq(uploads.id, record.id)) + logger.info('Removed abandoned upload', { + id: record.id, + cid: record.contentCid, + }) + } catch (error) { + logger.error('Failed to remove abandoned upload', { + id: record.id, + cid: record.contentCid, + error: error instanceof Error ? error.message : String(error), + }) + } + } + + return res + .status(200) + .json({ success: true, message: 'Abandoned uploads cleaned up' }) + } catch (error) { + logger.error('Error in deleteAbandonedUploads job', { + error: error instanceof Error ? error.message : String(error), + }) + return res + .status(500) + .json({ success: false, message: 'Failed to clean up abandoned uploads' }) + } +} + /** * daily usage snapshot job * runs every day at 9am UTC @@ -174,13 +222,8 @@ export const deleteExpiredUploads = async (_req: Request, res: Response) => { export const dailyUsageSnapshot = async (_req: Request, res: Response) => { try { logger.info('running daily usage snapshot job') - - const storachaClient = await initStorachaClient() - const usageService = new UsageService(storachaClient) - await usageService.initialize() - + const usageService = new UsageService() await usageService.createDailySnapshot() - return res.status(200).json({ success: true, message: 'daily usage snapshot completed', @@ -201,13 +244,8 @@ export const dailyUsageSnapshot = async (_req: Request, res: Response) => { export const weeklyUsageComparison = async (_req: Request, res: Response) => { try { logger.info('running weekly usage comparison job') - - const storachaClient = await initStorachaClient() - const usageService = new UsageService(storachaClient) - await usageService.initialize() - + const usageService = new UsageService() await usageService.compareUsage() - return res.status(200).json({ success: true, message: 'weekly usage comparison completed', diff --git a/server/src/controllers/pricing.controller.ts b/server/src/controllers/pricing.controller.ts index c3e9ba6..04ca77f 100644 --- a/server/src/controllers/pricing.controller.ts +++ b/server/src/controllers/pricing.controller.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express' import { getSolPrice } from '../services/price/sol-price.service.js' import { PaymentChain, QuoteOutput } from '../types.js' import { logger } from '../utils/logger.js' -import { getQuoteForFileUpload } from '../utils/storacha.js' +import { getQuoteForFileUpload } from '../utils/pricing.js' /** * Function to get Quote For File Upload diff --git a/server/src/controllers/storage.controller.ts b/server/src/controllers/storage.controller.ts index 8cc22a2..065b6c2 100644 --- a/server/src/controllers/storage.controller.ts +++ b/server/src/controllers/storage.controller.ts @@ -3,11 +3,12 @@ import { eq } from 'drizzle-orm' import { Request, Response } from 'express' import { db } from '../db/db.js' import { configTable, uploads } from '../db/schema.js' -import { renewStorageDuration, saveTransaction } from '../db/uploads-table.js' +import { renewStorageDuration, saveTransaction } from '../db/uploads.js' import { getUsdfcContractAddress, verifyErc20Transfer, } from '../services/fil/verify.service.js' +import { gatewayUrl } from '../services/storage/pinata.service.js' import { getSolPrice } from '../services/price/sol-price.service.js' import { getAmountInLamportsFromUSD, @@ -17,7 +18,7 @@ import { ONE_BILLION_LAMPORTS, } from '../utils/constant.js' import { logger } from '../utils/logger.js' -import { getPricingConfig } from '../utils/storacha.js' +import { getPricingConfig } from '../utils/pricing.js' import { createStorageRenewalTransaction } from './solana.controller.js' /** @@ -226,6 +227,12 @@ export const confirmStorageRenewal = async (req: Request, res: Response) => { return res.status(200).json({ message: 'Storage renewed successfully', deposit: updated, + url: gatewayUrl( + updated.contentCid, + updated.fileType === 'directory' + ? undefined + : (updated.fileName ?? undefined), + ), }) } catch (error) { logger.error('Error confirming renewal', { @@ -391,6 +398,12 @@ export const confirmRenewalUsdFC = async (req: Request, res: Response) => { verified: true, message: 'Storage renewed successfully with USDFC', deposit: updated, + url: gatewayUrl( + updated.contentCid, + updated.fileType === 'directory' + ? undefined + : (updated.fileName ?? undefined), + ), }) } catch (error) { Sentry.captureException(error) diff --git a/server/src/controllers/transactions.controller.ts b/server/src/controllers/transactions.controller.ts index 324077d..df7713b 100644 --- a/server/src/controllers/transactions.controller.ts +++ b/server/src/controllers/transactions.controller.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express' -import { getTransactionsForCID } from '../db/uploads-table.js' +import { getTransactionsForCID } from '../db/uploads.js' import { logger } from '../utils/logger.js' /** diff --git a/server/src/controllers/upload.controller.ts b/server/src/controllers/upload.controller.ts index a1505da..400d903 100644 --- a/server/src/controllers/upload.controller.ts +++ b/server/src/controllers/upload.controller.ts @@ -4,21 +4,21 @@ import { eq } from 'drizzle-orm' import { Request, Response } from 'express' import { db } from '../db/db.js' import { configTable, uploads } from '../db/schema.js' -import { getUserHistory, saveTransaction } from '../db/uploads-table.js' +import { getUserHistory, saveTransaction } from '../db/uploads.js' import { getUsdfcContractAddress, verifyErc20Transfer, } from '../services/fil/verify.service.js' import { getSolPrice } from '../services/price/sol-price.service.js' import { PaginationContext } from '../types.js' -import { computeCID } from '../utils/compute-cid.js' import { DAY_TIME_IN_SECONDS, getAmountInLamportsFromUSD, } from '../utils/constant.js' import { getExpiryDate, getPaginationParams } from '../utils/functions.js' import { logger } from '../utils/logger.js' -import { getPricingConfig, initStorachaClient } from '../utils/storacha.js' +import { getPricingConfig } from '../utils/pricing.js' +import { gatewayUrl, pinFiles } from '../services/storage/pinata.service.js' import { createDepositTransaction } from './solana.controller.js' const MIN_DURATION_SECONDS = DAY_TIME_IN_SECONDS // 1 day @@ -26,17 +26,7 @@ const MIN_DURATION_SECONDS = DAY_TIME_IN_SECONDS // 1 day const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ /** - * i encountered an issue yesterday where uploading a 73MB folder took around 20.2mins and still ended up - * failing because of a timeout. the entire 73MB CAR was sent directly via blob/add on - * a slower connection 70Mbps download 5/10Mbps upload speed. we should break large files into smaller - * chunks by specifying the shard size. - * in this case, a 10MB (10 * 1024 * 1024 = 10,485,760` bytes) shard size would mean having around ~8 shards - * many requests. but each one finishes faster compared to the 73MB (and potentially large sizes in the future) chunk at once - */ -const SHARD_SIZE = 10_485_760 - -/** - * Function to upload a file to storacha + * Function to pin a file to IPFS via Pinata */ export const uploadFile = async (req: Request, res: Response) => { try { @@ -46,30 +36,26 @@ export const uploadFile = async (req: Request, res: Response) => { } const cid = req.query.cid as string if (!cid) return res.status(400).json({ message: 'CID is required' }) - const files = [ - new File([file.buffer], file.originalname, { type: file.mimetype }), - ] - const client = await initStorachaClient() - const uploadedCID = await client.uploadFile(files[0]) - - if (uploadedCID.toString() !== cid) { - throw new Error( - `CID mismatch! Precomputed: ${cid}, Uploaded: ${uploadedCID}`, - ) - } + const pinnedCID = await pinFiles( + { + [file.originalname]: { + buffer: new Uint8Array(file.buffer), + mimetype: file.mimetype, + }, + }, + file.originalname, + ) - const uploadObject = { - cid: uploadedCID, - filename: file.originalname, - size: file.size, - type: file.mimetype, - url: `https://w3s.link/ipfs/${cid}/${file.originalname}`, - uploadedAt: new Date().toISOString(), + if (pinnedCID !== cid) { + logger.warn('CID mismatch between pre-computed and pinned', { + precomputed: cid, + pinned: pinnedCID, + }) } Sentry.setContext('file-upload', { - cid, + cid: cid, fileName: file.originalname, fileSize: file.size, mimeType: file.mimetype, @@ -77,25 +63,32 @@ export const uploadFile = async (req: Request, res: Response) => { res.status(200).json({ message: 'Upload successful', - cid: uploadedCID, - object: uploadObject, + cid: cid, + object: { + cid: cid, + filename: file.originalname, + size: file.size, + type: file.mimetype, + url: gatewayUrl(cid, file.originalname), + uploadedAt: new Date().toISOString(), + }, }) } catch (error: any) { Sentry.captureException(error) - logger.error('Error uploading file to Storacha', { + logger.error('Error uploading file', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, cause: error?.cause, }) res.status(400).json({ - message: 'Error uploading file to directory', + message: 'Error uploading file', error: error instanceof Error ? error.message : String(error), }) } } /** - * Allows upload of multiple files or a directory to storacha + * Pins multiple files or a directory to IPFS via Pinata */ export const uploadFiles = async (req: Request, res: Response) => { try { @@ -106,19 +99,24 @@ export const uploadFiles = async (req: Request, res: Response) => { const cid = req.query.cid as string if (!cid) return res.status(400).json({ message: 'CID is required' }) - const fileObjects = files.map( - (f) => new File([f.buffer], f.originalname, { type: f.mimetype }), - ) + const fileMap: Record = {} + for (const f of files) { + fileMap[f.originalname] = { + buffer: new Uint8Array(f.buffer), + mimetype: f.mimetype, + } + } - const client = await initStorachaClient() - const uploadedCID = await client.uploadDirectory(fileObjects, { - shardSize: SHARD_SIZE, - }) + const pinnedCID = await pinFiles( + fileMap, + `directory-${crypto.randomUUID()}`, + ) - if (uploadedCID.toString() !== cid) - throw new Error( - `CID mismatch! Computed: ${cid}, Uploaded: ${uploadedCID}`, - ) + if (pinnedCID !== cid) + logger.warn('CID mismatch between pre-computed and pinned', { + precomputed: cid, + pinned: pinnedCID, + }) Sentry.setContext('multi-file-upload', { cid, @@ -128,24 +126,21 @@ export const uploadFiles = async (req: Request, res: Response) => { }) Sentry.setTag('operation', 'multi-file-upload') - const uploadObject = { - cid: uploadedCID, - directoryName: `Upload-${crypto.randomUUID()}`, - url: `https://w3s.link/ipfs/${cid}`, - size: files.reduce((sum, f) => sum + f.size, 0), - files: files.map((f) => ({ - filename: f.originalname, - size: f.size, - type: f.mimetype, - url: `https://w3s.link/ipfs/${cid}/${f.originalname}`, - })), - uploadedAt: new Date().toISOString(), - } - res.status(200).json({ message: 'Upload successful', - cid: uploadedCID, - object: uploadObject, + cid, + object: { + cid, + url: gatewayUrl(cid), + size: files.reduce((sum, f) => sum + f.size, 0), + files: files.map((f) => ({ + filename: f.originalname, + size: f.size, + type: f.mimetype, + url: gatewayUrl(cid, f.originalname), + })), + uploadedAt: new Date().toISOString(), + }, }) } catch (error: any) { Sentry.captureException(error) @@ -220,12 +215,17 @@ export const deposit = async (req: Request, res: Response) => { amountInLamports, }) - const computedCID = await computeCID(fileMap) + const pinnedCID = await pinFiles( + fileMap, + fileArray.length === 1 + ? fileArray[0].originalname + : directoryName || `dir-${Date.now()}`, + ) const existingUpload = await db .select() .from(uploads) - .where(eq(uploads.contentCid, computedCID)) + .where(eq(uploads.contentCid, pinnedCID)) .limit(1) if (existingUpload.length > 0 && existingUpload[0].transactionHash) @@ -239,7 +239,7 @@ export const deposit = async (req: Request, res: Response) => { totalSize, fileCount: fileArray.length, duration: duration_days, - cid: computedCID, + cid: pinnedCID, }) Sentry.setTag('operation', 'deposit') @@ -249,46 +249,69 @@ export const deposit = async (req: Request, res: Response) => { throw new Error(`Invalid deposit amount calculated: ${amountInLamports}`) } + const expiresAt = getExpiryDate(duration_days) + const fileName = + fileArray.length === 1 ? fileArray[0].originalname : directoryName || null + const fileType = + fileArray.length === 1 ? fileArray[0].mimetype : 'directory' + + if (existingUpload.length === 0) { + await db.insert(uploads).values({ + depositAmount: amountInLamports, + durationDays: duration_days, + contentCid: pinnedCID, + depositKey: publicKey, + depositSlot: 1, + lastClaimedSlot: 1, + expiresAt, + createdAt: new Date().toISOString(), + userEmail: sanitizedEmail || null, + fileName, + fileType, + fileSize: totalSize, + transactionHash: null, + deletionStatus: 'pending', + warningSentAt: null, + paymentChain: 'sol', + paymentToken: 'SOL', + }) + } else { + // pending record exists — refresh metadata in case user retries with updated params + await db + .update(uploads) + .set({ + depositAmount: amountInLamports, + durationDays: duration_days, + depositKey: publicKey, + expiresAt, + userEmail: sanitizedEmail || null, + fileName, + fileType, + fileSize: totalSize, + deletionStatus: 'pending', + }) + .where(eq(uploads.contentCid, pinnedCID)) + } + const depositInstructions = await createDepositTransaction({ publicKey, fileSize: totalSize, - contentCID: computedCID, + contentCID: pinnedCID, durationDays: duration_days, depositAmount: amountInLamports, }) - const backupExpirationDate = getExpiryDate(duration_days) - - // pass deposit meta later in the upload flow for db writes - // after a succesful confirmation - const depositMetadata = { - depositAmount: amountInLamports, - durationDays: duration_days, - depositKey: publicKey, - userEmail: sanitizedEmail || null, - fileName: - fileArray.length === 1 - ? fileArray[0].originalname - : directoryName || null, - fileType: fileArray.length === 1 ? fileArray[0].mimetype : 'directory', - fileSize: totalSize, - expiresAt: backupExpirationDate, - paymentChain: 'sol', - paymentToken: 'SOL', - } - res.status(200).json({ message: 'Deposit instruction ready — sign to finalize upload', - cid: computedCID, + cid: pinnedCID, instructions: depositInstructions, fileCount: fileArray.length, - totalSize: totalSize, + totalSize, files: fileArray.map((f) => ({ name: f.originalname, size: f.size, type: f.mimetype, })), - depositMetadata, }) } catch (error) { Sentry.captureException(error) @@ -305,7 +328,7 @@ export const deposit = async (req: Request, res: Response) => { type FileMeta = { totalSize: number fileArray: Express.Multer.File[] - fileMap: Record + fileMap: Record } /** @@ -339,11 +362,14 @@ const fileBuilder = ( if (fileArray.length === 0) throw new Error('No files selected') - const fileMap: Record = {} + const fileMap: Record = {} let totalSize = 0 for (const file of fileArray) { - fileMap[file.originalname] = new Uint8Array(file.buffer) + fileMap[file.originalname] = { + buffer: new Uint8Array(file.buffer), + mimetype: file.mimetype, + } totalSize += file.size } @@ -390,12 +416,25 @@ export const depositUsdFC = async (req: Request, res: Response) => { amountInUSDFC: amountInUSDFC.toString(), }) - const computedCID = await computeCID(fileMap) + // this is a reference to what i've seen in the filecoin-pin repo. + // javascript has another number type, apparently — BigNum/Int + if (amountInUSDFC <= 0n) + throw new Error(`Invalid deposit amount calculated: ${amountInUSDFC}`) + + const durationNum = Number(duration) + if (!Number.isFinite(durationNum)) throw new Error('Invalid duration') + + const pinnedCID = await pinFiles( + fileMap, + fileArray.length === 1 + ? fileArray[0].originalname + : directoryName || `dir-${Date.now()}`, + ) const existingUpload = await db .select() .from(uploads) - .where(eq(uploads.contentCid, computedCID)) + .where(eq(uploads.contentCid, pinnedCID)) .limit(1) if (existingUpload.length > 0 && existingUpload[0].transactionHash) @@ -409,7 +448,7 @@ export const depositUsdFC = async (req: Request, res: Response) => { totalSize, fileCount: fileArray.length, duration: duration_days, - cid: computedCID, + cid: pinnedCID, chain: 'FIL', }) @@ -417,30 +456,48 @@ export const depositUsdFC = async (req: Request, res: Response) => { Sentry.setTag('file_count', fileArray.length) Sentry.setTag('payment_chain', 'fil') - // this is a reference to what i've seen in the filecoin-pin repo. - // javascript has another number type, apparently — BigNum/Int - if (amountInUSDFC <= 0n) - throw new Error(`Invalid deposit amount calculated: ${amountInUSDFC}`) - - const durationNum = Number(duration) - if (!Number.isFinite(durationNum)) throw new Error('Invalid duration') - - const backupExpirationDate = getExpiryDate(duration_days) - - const depositMetadata = { - depositAmount: amountInUSDFC.toString(), - durationDays: duration_days, - depositKey: userAddress, - userEmail: userEmail || null, - fileName: - fileArray.length === 1 - ? fileArray[0].originalname - : directoryName || null, - fileType: fileArray.length === 1 ? fileArray[0].mimetype : 'directory', - fileSize: totalSize, - expiresAt: backupExpirationDate, - paymentChain: 'fil', - paymentToken: 'USDFC', + const expiresAt = getExpiryDate(duration_days) + const fileName = + fileArray.length === 1 ? fileArray[0].originalname : directoryName || null + const fileType = + fileArray.length === 1 ? fileArray[0].mimetype : 'directory' + + if (existingUpload.length === 0) { + await db.insert(uploads).values({ + depositAmount: Number(amountInUSDFC), + durationDays: duration_days, + contentCid: pinnedCID, + depositKey: userAddress, + depositSlot: 0, + lastClaimedSlot: 0, + expiresAt, + createdAt: new Date().toISOString(), + userEmail: userEmail || null, + fileName, + fileType, + fileSize: totalSize, + transactionHash: null, + deletionStatus: 'pending', + warningSentAt: null, + paymentChain: 'fil', + paymentToken: 'USDFC', + }) + } else { + // pending record exists — refresh metadata in case user retries with updated params + await db + .update(uploads) + .set({ + depositAmount: Number(amountInUSDFC), + durationDays: duration_days, + depositKey: userAddress, + expiresAt, + userEmail: userEmail || null, + fileName, + fileType, + fileSize: totalSize, + deletionStatus: 'pending', + }) + .where(eq(uploads.contentCid, pinnedCID)) } const isMainnet = process.env.NODE_ENV === 'production' @@ -450,18 +507,17 @@ export const depositUsdFC = async (req: Request, res: Response) => { res.status(200).json({ message: 'Payment details ready — transfer USDFC to proceed with upload', - cid: computedCID, + cid: pinnedCID, amountUSDFC: amountInUSDFC.toString(), recipientAddress: config[0].filecoinWallet, usdfcContractAddress, fileCount: fileArray.length, - totalSize: totalSize, + totalSize, files: fileArray.map((f) => ({ name: f.originalname, size: f.size, type: f.mimetype, })), - depositMetadata, }) } catch (error) { Sentry.captureException(error) @@ -519,11 +575,12 @@ export const getUploadHistory = async (req: Request, res: Response) => { } /** - * Function to create DB record and save transaction hash after payment is confirmed + * Marks a pending upload as confirmed after the Solana transaction is verified. + * The file is already pinned on Pinata from the deposit step. */ export const confirmUpload = async (req: Request, res: Response) => { try { - const { cid, transactionHash, depositMetadata } = req.body + const { cid, transactionHash } = req.body if (!cid || !transactionHash) { return res.status(400).json({ @@ -531,83 +588,50 @@ export const confirmUpload = async (req: Request, res: Response) => { }) } - if (!depositMetadata) { - return res.status(400).json({ - message: 'Deposit metadata is required', - }) - } - const existing = await db .select() .from(uploads) .where(eq(uploads.contentCid, cid)) .limit(1) - if (existing.length > 0) { - // exists but has no transaction hash, update it - if (!existing[0].transactionHash) { - const updated = await db - .update(uploads) - .set({ transactionHash: transactionHash }) - .where(eq(uploads.contentCid, cid)) - .returning() - - await saveTransaction({ - depositId: updated[0].id, - contentCid: cid, - transactionHash: transactionHash, - transactionType: 'initial_deposit', - amountInLamports: updated[0].depositAmount, - durationDays: updated[0].durationDays, - }) - - return res.status(200).json({ - verified: true, - message: 'Transaction hash updated successfully', - deposit: updated[0], - }) - } + if (existing.length === 0) + return res.status(404).json({ + message: 'No pending upload found for this CID', + }) + if (existing[0].transactionHash) return res.status(409).json({ message: 'This upload has already been confirmed', deposit: existing[0], }) - } - - const depositItem: typeof uploads.$inferInsert = { - depositAmount: depositMetadata.depositAmount, - durationDays: depositMetadata.durationDays, - contentCid: cid, - depositKey: depositMetadata.depositKey, - depositSlot: 1, - lastClaimedSlot: 1, - expiresAt: depositMetadata.expiresAt, - createdAt: new Date().toISOString(), - userEmail: depositMetadata.userEmail, - fileName: depositMetadata.fileName, - fileType: depositMetadata.fileType, - fileSize: depositMetadata.fileSize, - transactionHash: transactionHash, - deletionStatus: 'active', - warningSentAt: null, - paymentChain: depositMetadata.paymentChain || 'sol', - paymentToken: depositMetadata.paymentToken || 'SOL', - } - const inserted = await db.insert(uploads).values(depositItem).returning() + const [confirmedUpload] = await db + .update(uploads) + .set({ transactionHash, deletionStatus: 'active' }) + .where(eq(uploads.contentCid, cid)) + .returning() await saveTransaction({ - depositId: inserted[0].id, + depositId: confirmedUpload.id, contentCid: cid, - transactionHash: transactionHash, + transactionHash, transactionType: 'initial_deposit', - amountInLamports: inserted[0].depositAmount, - durationDays: inserted[0].durationDays, + amountInLamports: confirmedUpload.depositAmount, + durationDays: confirmedUpload.durationDays, }) + const url = gatewayUrl( + cid, + confirmedUpload.fileType === 'directory' + ? undefined + : (confirmedUpload.fileName ?? undefined), + ) + return res.status(200).json({ - message: 'Upload confirmed and saved successfully', - deposit: inserted[0], + verified: true, + message: 'Upload confirmed successfully', + deposit: confirmedUpload, + url, }) } catch (err) { Sentry.captureException(err) @@ -626,25 +650,24 @@ export const confirmUpload = async (req: Request, res: Response) => { * * @param req.body.cid - Content identifier of the uploaded files * @param req.body.transactionHash - Filecoin transaction hash of the USDFC transfer - * @param req.body.depositMetadata - Metadata from depositUsdFC response * @returns Confirmation message with deposit record * * @remarks * Transaction verification will be implemented with indexer (see #176). - * SDK handles file upload to Storacha separately via /upload/file(s) endpoints. + * SDK handles file pinning to IPFS via Pinata via /upload/file(s) endpoints. + */ +/** + * Verifies USDFC payment transaction and marks the pending upload as confirmed. + * The file is already pinned on Pinata from the deposit step. */ export const verifyUsdFcPayment = async (req: Request, res: Response) => { try { - const { cid, transactionHash, depositMetadata } = req.body + const { cid, transactionHash } = req.body if (!cid || !transactionHash) return res.status(400).json({ message: 'The CID and transaction hash are required', }) - if (!depositMetadata) - return res.status(400).json({ - message: 'Deposit metadata for this USDFC transaction is required', - }) const existing = await db .select() @@ -652,96 +675,54 @@ export const verifyUsdFcPayment = async (req: Request, res: Response) => { .where(eq(uploads.contentCid, cid)) .limit(1) - if (existing.length > 0) { - if (!existing[0].transactionHash) { - const updated = await db - .update(uploads) - .set({ transactionHash: transactionHash }) - .where(eq(uploads.contentCid, cid)) - .returning() - - await saveTransaction({ - depositId: updated[0].id, - contentCid: cid, - transactionHash: transactionHash, - transactionType: 'initial_deposit', - amountInLamports: updated[0].depositAmount, - durationDays: updated[0].durationDays, - }) - - return res.status(200).json({ - message: 'Transaction hash updated successfully', - deposit: updated[0], - }) - } + if (existing.length === 0) + return res.status(404).json({ + message: 'No pending upload found for this CID', + }) + if (existing[0].transactionHash) return res.status(409).json({ message: 'This upload has already been confirmed', deposit: existing[0], }) - } const config = await db.select().from(configTable) if (!config[0].filecoinWallet) throw new Error('Filecoin wallet not configured') - const treasury = config[0].filecoinWallet - const userAddress = depositMetadata.depositKey - - const USDFC_CONTRACT = getUsdfcContractAddress() - - const expectedAmount = BigInt(depositMetadata.depositAmount) - const { verified } = await verifyErc20Transfer({ transactionHash, - from: userAddress, - to: treasury, - contractAddress: USDFC_CONTRACT, - expectedAmount, + from: existing[0].depositKey, + to: config[0].filecoinWallet, + contractAddress: getUsdfcContractAddress(), + expectedAmount: BigInt(existing[0].depositAmount), }) - if (!verified) { + if (!verified) return res.status(400).json({ message: 'USDFC transfer verification failed. Transaction may not exist, may have failed, or the amount/recipient does not match.', }) - } - - const depositItem: typeof uploads.$inferInsert = { - depositAmount: depositMetadata.depositAmount, - durationDays: depositMetadata.durationDays, - contentCid: cid, - depositKey: depositMetadata.depositKey, - depositSlot: 0, - lastClaimedSlot: 0, - expiresAt: depositMetadata.expiresAt, - createdAt: new Date().toISOString(), - userEmail: depositMetadata.userEmail, - fileName: depositMetadata.fileName, - fileType: depositMetadata.fileType, - fileSize: depositMetadata.fileSize, - transactionHash: transactionHash, - deletionStatus: 'active', - warningSentAt: null, - paymentChain: 'fil', - paymentToken: 'USDFC', - } - const inserted = await db.insert(uploads).values(depositItem).returning() + const [confirmedUpload] = await db + .update(uploads) + .set({ transactionHash, deletionStatus: 'active' }) + .where(eq(uploads.contentCid, cid)) + .returning() await saveTransaction({ - depositId: inserted[0].id, + depositId: confirmedUpload.id, contentCid: cid, - transactionHash: transactionHash, + transactionHash, transactionType: 'initial_deposit', - amountInLamports: Number(depositMetadata.depositAmount), - durationDays: inserted[0].durationDays, + amountInLamports: confirmedUpload.depositAmount, + durationDays: confirmedUpload.durationDays, }) return res.status(200).json({ verified: true, message: 'USDFC payment verified and upload confirmed successfully', - deposit: inserted[0], + deposit: confirmedUpload, }) } catch (error) { Sentry.captureException(error) diff --git a/server/src/controllers/user.controller.ts b/server/src/controllers/user.controller.ts deleted file mode 100644 index 95d826b..0000000 --- a/server/src/controllers/user.controller.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Capabilities } from '@storacha/client/types' -import { DID } from '@ucanto/core' -import * as Delegation from '@ucanto/core/delegation' -import { Link } from '@ucanto/core/schema' -import { Request, Response } from 'express' -import { logger } from '../utils/logger.js' -import { initStorachaClient } from '../utils/storacha.js' - -/** - * Function to create UCAN delegation to grant access of a space to an agent - * @param req - * @param res - * @returns - */ -export const createUCANDelegation = async (req: Request, res: Response) => { - try { - const { recipientDID, deadline, notBefore, baseCapabilities, fileCID } = - req.body - const client = await initStorachaClient() - const spaceDID = client.agent.did() - const audience = DID.parse(recipientDID) - const agent = client.agent - const capabilities: Capabilities = baseCapabilities.map((can: string) => ({ - with: `${spaceDID}`, - can, - nb: { - root: Link.parse(fileCID), - }, - })) - - const ucan = await Delegation.delegate({ - issuer: agent.issuer, - audience, - expiration: Number(deadline), - notBefore: Number(notBefore), - capabilities, - }) - - const archive = await ucan.archive() - if (!archive.ok) { - throw new Error('Failed to create delegation archive') - } - - return res.status(200).json({ - message: 'Delegation created successfully', - delegation: Buffer.from(archive.ok).toString('base64'), - }) - } catch (err) { - logger.error('Error creating UCAN delegation', { - error: err instanceof Error ? err.message : String(err), - }) - return res.status(500).json({ error: 'Failed to create delegation' }) - } -} diff --git a/server/src/db/uploads-table.ts b/server/src/db/uploads.ts similarity index 87% rename from server/src/db/uploads-table.ts rename to server/src/db/uploads.ts index 0c45222..bb61941 100644 --- a/server/src/db/uploads-table.ts +++ b/server/src/db/uploads.ts @@ -1,4 +1,5 @@ -import { and, desc, eq, inArray, isNull, lte, or, sql } from 'drizzle-orm' +import { and, desc, eq, inArray, isNull, lt, lte, or, sql } from 'drizzle-orm' +import { gatewayUrl } from '../services/storage/pinata.service.js' import { PaginationContext } from '../types.js' import { logger } from '../utils/logger.js' import { db } from './db.js' @@ -59,8 +60,18 @@ export const getUserHistory = async ( const buildPageUrl = (p: number) => `${ctx?.baseUrl}${ctx?.path}?userAddress=${userAddress}&page=${p}&limit=${limit}&chain=${chain}` + const records = data.map((record) => ({ + ...record, + url: gatewayUrl( + record.contentCid, + record.fileType === 'directory' + ? undefined + : (record.fileName ?? undefined), + ), + })) + return { - data, + data: records, total, page, pageSize: limit, @@ -269,6 +280,39 @@ export const renewStorageDuration = async (cid: string, duration: number) => { } } +/** + * Find pending uploads older than the given age in hours. + * These are files pinned to Pinata but never paid for — safe to unpin and remove. + * @param abandonedAfterHowManyHours - Age threshold in hours (default: 24) + */ +export const getAbandonedPendingUploads = async ( + abandonedAfterHowManyHours: number = 24, +) => { + try { + const cutoff = new Date( + Date.now() - abandonedAfterHowManyHours * 60 * 60 * 1000, + ).toISOString() + + const records = await db + .select() + .from(uploads) + .where( + and( + eq(uploads.deletionStatus, 'pending'), + isNull(uploads.transactionHash), + lt(uploads.createdAt, cutoff), + ), + ) + + return records + } catch (err) { + logger.error('Error getting abandoned pending uploads', { + error: err instanceof Error ? err.message : String(err), + }) + return null + } +} + /** * Save a transaction for an upload */ diff --git a/server/src/index.ts b/server/src/index.ts index 55e7354..16ef537 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -15,7 +15,6 @@ import { solanaRouter } from './routes/solana.route.js' import { storageRouter } from './routes/storage.route.js' import { transactionsRouter } from './routes/transactions.route.js' import { uploadsRouter } from './routes/upload.route.js' -import { userRouter } from './routes/user.route.js' import { logger } from './utils/logger.js' import { ensureConfigInitialized } from './utils/solana/index.js' @@ -32,6 +31,8 @@ function validateEnv() { 'QSTASH_CURRENT_SIGNING_KEY', 'QSTASH_NEXT_SIGNING_KEY', 'RESEND_API_KEY', + 'PINATA_JWT', + 'PINATA_GATEWAY', ] const missing = requiredVars.filter((key) => !process.env[key]) @@ -88,7 +89,6 @@ app.use(apiLimiter) app.use('/console', consoleRouter) app.use('/upload', uploadsRouter) app.use('/storage', storageRouter) -app.use('/user', userRouter) app.use('/solana', solanaRouter) app.use('/jobs', jobsRouter) app.use('/health', serverRouter) diff --git a/server/src/middlewares/x402.middleware.ts b/server/src/middlewares/x402.middleware.ts index 81892da..aa5b76d 100644 --- a/server/src/middlewares/x402.middleware.ts +++ b/server/src/middlewares/x402.middleware.ts @@ -4,7 +4,7 @@ import { ExactEvmScheme } from '@x402/evm/exact/server' import { paymentMiddleware } from '@x402/express' import { getAmountInUSD } from '../utils/constant.js' import { logger } from '../utils/logger.js' -import { getPricingConfig } from '../utils/storacha.js' +import { getPricingConfig } from '../utils/pricing.js' const isMainnet = process.env.X402_NETWORK === 'mainnet' const BASE_NETWORK = isMainnet ? 'eip155:8453' : 'eip155:84532' diff --git a/server/src/routes/jobs.route.ts b/server/src/routes/jobs.route.ts index 88d440d..ad251e2 100644 --- a/server/src/routes/jobs.route.ts +++ b/server/src/routes/jobs.route.ts @@ -16,6 +16,12 @@ jobs.post( jobsController.deleteExpiredUploads, ) +jobs.post( + '/remove-debtors', + verifyQStashRequest, + jobsController.deleteAbandonedUploads, +) + jobs.post( '/usage/snapshot', verifyQStashRequest, diff --git a/server/src/routes/user.route.ts b/server/src/routes/user.route.ts deleted file mode 100644 index 045e527..0000000 --- a/server/src/routes/user.route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import express from 'express' -import * as userController from '../controllers/user.controller.js' - -export const userRouter = express.Router() - -userRouter.post('/create-delegation', userController.createUCANDelegation) diff --git a/server/src/services/storage/pinata.service.ts b/server/src/services/storage/pinata.service.ts new file mode 100644 index 0000000..97362f6 --- /dev/null +++ b/server/src/services/storage/pinata.service.ts @@ -0,0 +1,71 @@ +import { PinataSDK, pinnedFileCount, totalStorageUsage } from 'pinata' +import { logger } from '../../utils/logger.js' + +function getClient(): PinataSDK { + const jwt = process.env.PINATA_JWT + if (!jwt) throw new Error('PINATA_JWT env var not set') + return new PinataSDK({ pinataJwt: jwt }) +} + +/** + * Returns a gateway URL for a CID. + * Requires PINATA_GATEWAY env var to be set. + */ +export function gatewayUrl(cid: string, filename?: string): string { + const base = process.env.PINATA_GATEWAY + if (!base) throw new Error('PINATA_GATEWAY env var not set') + return filename + ? `${base}/ipfs/${cid}/${encodeURIComponent(filename)}` + : `${base}/ipfs/${cid}` +} + +/** + * Pins one or more files as a directory to Pinata. + * Always uses fileArray so the returned CID is a directory CID, + * matching what computeCID produces via createDirectoryEncoderStream. + */ +export async function pinFiles( + fileMap: Record, + directoryName: string, +): Promise { + const pinata = getClient() + const files = Object.entries(fileMap).map( + ([name, { buffer, mimetype }]) => + new File([buffer], name, { type: mimetype }), + ) + const result = await pinata.upload.public.fileArray(files).name(directoryName) + logger.info('pinata: directory pinned', { cid: result.cid, directoryName }) + return result.cid +} + +/** + * Removes a pin from Pinata by CID. + * Looks up the Pinata file ID first since the delete API takes IDs, not CIDs. + */ +export async function unpinCID(cid: string): Promise { + const pinata = getClient() + const list = await pinata.files.public.list().cid(cid) + if (!list.files.length) { + logger.warn('pinata: no file found for CID, skipping unpin', { cid }) + return + } + const fileId = list.files[0].id + await pinata.files.public.delete([fileId]) + logger.info('pinata: CID unpinned', { cid, fileId }) +} + +/** + * Returns total pinned file count and storage bytes from Pinata. + * Used by the usage monitoring service. + */ +export async function getPinataUsage(): Promise<{ + pinCount: number + totalSizeBytes: number +}> { + const pinata = getClient() + const [pinCount, totalSizeBytes] = await Promise.all([ + pinnedFileCount(pinata.config), + totalStorageUsage(pinata.config), + ]) + return { pinCount, totalSizeBytes } +} diff --git a/server/src/services/usage/usage.service.ts b/server/src/services/usage/usage.service.ts index 9e30fab..103607e 100644 --- a/server/src/services/usage/usage.service.ts +++ b/server/src/services/usage/usage.service.ts @@ -1,5 +1,3 @@ -import { Client as StorachaClient } from '@storacha/client' -import { AccountDID, PlanGetSuccess } from '@storacha/client/types' import { eq, sql } from 'drizzle-orm' import { Resend } from 'resend' import { db } from '../../db/db.js' @@ -9,6 +7,7 @@ import { usageAlerts, usageComparison, } from '../../db/schema.js' +import { getPinataUsage } from '../storage/pinata.service.js' import { logger } from '../../utils/logger.js' const { EMAIL_FROM, WATCHMAN } = process.env! @@ -22,39 +21,13 @@ const recipients: string | string[] = (() => { })() const resend = new Resend(process.env.RESEND_API_KEY) -const KiB = 1024 -const MiB = 1024 * KiB -const GiB = 1024 * MiB -const TiB = 1024 * GiB - -/** - * known plan capacities by product DID. - * - * storacha plans with overages enabled return limit: 0 from plan/get, - * meaning "unlimited with overage charges." the actual included capacity - * must be mapped from the product DID. for forge plans (strict limits), - * plan/get returns the real byte limit. - * - * see: https://github.com/storacha/specs/pull/150 - */ -const PLAN_CAPACITIES: Record = { - 'did:web:starter.storacha.network': 5 * GiB, - 'did:web:lite.storacha.network': 100 * GiB, - 'did:web:business.storacha.network': 2 * TiB, -} - -interface UsageReport { - finalSize: number - initialSize: number -} - export class UsageService { - private client: StorachaClient private planLimitBytes: number - constructor(client: StorachaClient, planLimitBytes: number = 5 * GiB) { - this.client = client - this.planLimitBytes = planLimitBytes // default 5GiB size + constructor() { + // Default to 1GB free tier. Set PINATA_PLAN_LIMIT_MB env var when upgrading. + const mb = parseInt(process.env.PINATA_PLAN_LIMIT_MB ?? '1024', 10) + this.planLimitBytes = mb * 1024 * 1024 } get planLimit(): number { @@ -62,115 +35,17 @@ export class UsageService { } /** - * get plan limit from storacha using plan/get capability. - * - * if limit is 0, it means overages are enabled and the plan is - * virtually unlimited. we use the product DID to look up the - * included capacity instead. - * - * @returns limit in bytes, or null if unlimited + * Fetch current storage usage from Pinata. */ - private async fetchPlanLimit(): Promise { - try { - const accountDID = process.env.STORACHA_ACCOUNT_DID as AccountDID - - if (!accountDID) { - logger.warn( - 'STORACHA_ACCOUNT_DID not set, using default plan limit. Set STORACHA_ACCOUNT_DID in environment variables.', - ) - return 5 * GiB - } - - const plan: PlanGetSuccess = - await this.client.capability.plan.get(accountDID) - - // used to have TS complain about an error here before. - // previous comment no longer needed. this too would go away later. browse the commit - // history for your perusal if you want. - const limitBytes = parseInt(plan.limit, 10) - - // limit: 0 means overages enabled — look up included capacity from product DID - if (limitBytes === 0) { - const product = plan.product - const knownLimit = product ? PLAN_CAPACITIES[product] : undefined - - if (knownLimit) { - logger.info('plan has overages enabled, using known capacity', { - product, - limitBytes: knownLimit, - limitGB: (knownLimit / 1e9).toFixed(2), - }) - return knownLimit - } - - logger.warn('unknown product DID, using default capacity', { - product, - defaultGB: '5.00', - }) - return 5 * GiB - } - - // forge plans return the actual byte limit - logger.info('plan limit fetched from storacha', { - accountDID, - limitBytes, - limitGB: (limitBytes / 1e9).toFixed(2), - }) - - return limitBytes - } catch (error) { - logger.error('failed to fetch plan limit from storacha', { error }) - return 5 * GiB - } - } - - /** - * init the service - */ - async initialize(): Promise { - const fetchedLimit = await this.fetchPlanLimit() - if (fetchedLimit !== null) { - this.planLimitBytes = fetchedLimit - logger.info('usage service initialized with plan limit', { - limitBytes: this.planLimitBytes, - limitGB: (this.planLimitBytes / 1e9).toFixed(2), - }) - } else { - logger.info('usage service initialized with unlimited plan') - } - } - - /** - * fetch current usage from storacha using capability.usage.report - */ - async getStorachaUsage( - from: Date = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), - to: Date = new Date(), - ): Promise { + async getUsage(): Promise<{ + totalSizeBytes: number + pinCount: number + }> { try { - const currentSpace = this.client.currentSpace() - if (!currentSpace) throw new Error('no storacha space available') - - const spaceDid = currentSpace.did() - const report = await this.client.capability.usage.report(spaceDid, { - from, - to, - }) - - // report is Record, get the first/only entry - const usageData = Object.values(report)[0] - - if (!usageData) { - throw new Error('no usage data returned from storacha') - } - - return { - finalSize: usageData.size.final, - initialSize: usageData.size.initial, - } + return await getPinataUsage() } catch (error) { - logger.error('failed to fetch storacha usage report', { error }) - throw new Error('failed to fetch storacha usage') + logger.error('failed to fetch pinata usage', { error }) + throw new Error('failed to fetch pinata usage') } } @@ -200,21 +75,19 @@ export class UsageService { */ async createDailySnapshot(): Promise { try { - const [storachaUsage, internalUsage] = await Promise.all([ - this.getStorachaUsage(), + const [pinataUsage, internalUsage] = await Promise.all([ + this.getUsage(), this.calculateInternalUsage(), ]) - const storachaBytes = storachaUsage.finalSize + const storedBytes = pinataUsage.totalSizeBytes const utilization = - this.planLimitBytes > 0 - ? (storachaBytes / this.planLimitBytes) * 100 - : 0 + this.planLimitBytes > 0 ? (storedBytes / this.planLimitBytes) * 100 : 0 await db.insert(usage).values({ totalBytesStored: internalUsage.totalBytes, totalActiveUploads: internalUsage.activeUploads, - storachaReportedBytes: storachaBytes, + storachaReportedBytes: storedBytes, storachaPlanLimit: this.planLimitBytes, utilizationPercentage: utilization, }) @@ -222,15 +95,11 @@ export class UsageService { logger.info('daily usage snapshot created', { totalBytes: internalUsage.totalBytes, activeUploads: internalUsage.activeUploads, - storachaReportedBytes: storachaBytes, + pinataReportedBytes: storedBytes, utilization: `${utilization.toFixed(2)}%`, }) - await this.checkThresholds( - utilization, - storachaBytes, - this.planLimitBytes, - ) + await this.checkThresholds(storedBytes, this.planLimitBytes, utilization) } catch (error) { logger.error('failed to create daily snapshot', { error }) throw error @@ -238,17 +107,17 @@ export class UsageService { } /** - * weekly comparison between our data and the capability report + * weekly comparison between our internal data and the pinata report */ async compareUsage(): Promise { try { - const [storachaUsage, internalUsage] = await Promise.all([ - this.getStorachaUsage(), + const [pinataUsage, internalUsage] = await Promise.all([ + this.getUsage(), this.calculateInternalUsage(), ]) - const storachaBytes = storachaUsage.finalSize - const discrepancy = storachaBytes - internalUsage.totalBytes + const pinataBytes = pinataUsage.totalSizeBytes + const discrepancy = pinataBytes - internalUsage.totalBytes const discrepancyPercentage = internalUsage.totalBytes > 0 ? (Math.abs(discrepancy) / internalUsage.totalBytes) * 100 @@ -263,19 +132,19 @@ export class UsageService { await db.insert(usageComparison).values({ ourCalculatedBytes: internalUsage.totalBytes, - storachaReportedBytes: storachaBytes, + storachaReportedBytes: pinataBytes, discrepancyBytes: discrepancy, discrepancyPercentage, status, notes: status !== 'ok' - ? `discrepancy of ${(discrepancy / 1e9).toFixed(2)} GB (${discrepancyPercentage.toFixed(2)}%)` // 1e9 = 1GB in bytes + ? `discrepancy of ${(discrepancy / 1e9).toFixed(2)} GB (${discrepancyPercentage.toFixed(2)}%)` : null, }) logger.info('usage comparison completed', { ourBytes: internalUsage.totalBytes, - storachaBytes, + pinataBytes, discrepancy, status, }) @@ -297,9 +166,9 @@ export class UsageService { * check utilization thresholds and create alerts */ private async checkThresholds( - utilization: number, bytesStored: number, planLimit: number, + utilization: number, ): Promise { const thresholds = [ { level: 80, type: 'threshold_80', alertLevel: 'warning' as const }, @@ -309,7 +178,6 @@ export class UsageService { for (const threshold of thresholds) { if (utilization >= threshold.level) { - // check if alert already exists and is unresolved const existingAlert = await db .select() .from(usageAlerts) @@ -363,7 +231,7 @@ export class UsageService { ${alert.alertLevel === 'critical' ? '🔴' : '⚠️'} storage ${alert.alertLevel} alert

${alert.message}

- + ${ alert.utilizationPercentage ? ` @@ -385,7 +253,7 @@ export class UsageService { ` : '' } - +

alert type: ${alert.alertType}
created: ${new Date().toISOString()} diff --git a/server/src/tests/Admin/updateDuration.test.ts b/server/src/tests/Admin/updateDuration.test.ts deleted file mode 100644 index 79ba64f..0000000 --- a/server/src/tests/Admin/updateDuration.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import axios from 'axios' -import dotenv from 'dotenv' - -dotenv.config() - -const BASE_URL = `http://localhost:${process.env.PORT}/api/admin` -test('Update the Minimum Duration of the File Upload can be set by admin only', async () => { - const response = await axios.post( - `${BASE_URL}/updateMinDuration`, - { - duration: 30, // this should be in number of days - }, - { - headers: { - 'x-api-key': process.env.ADMIN_API_KEY, // Must match process.env.ADMIN_API_KEY - }, - }, - ) - // expect(typeof response.data.message).toBe(string); - expect(response.data.message).toBe( - 'Successfully updated the minimum Duration', - ) -}) - -test('Update the Rate for File Upload, can be set by admin only', async () => { - const result = await axios.post( - `${BASE_URL}/updateRate`, - { - rate: 40, - }, - { - headers: { - 'x-api-key': process.env.ADMIN_API_KEY, // Must match process.env.ADMIN_API_KEY - }, - }, - ) - expect(result.data.message).toBe('Successfully updated the rate per file') -}) diff --git a/server/src/tests/User/createDelegation.test.ts b/server/src/tests/User/createDelegation.test.ts deleted file mode 100644 index bfebb91..0000000 --- a/server/src/tests/User/createDelegation.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// __tests__/delegation.test.ts - -import axios from 'axios' -import dotenv from 'dotenv' - -dotenv.config() - -const BASE_URL = `http://localhost:${process.env.PORT}/api/user` - -test('Create delegation on behalf of the user', async () => { - const DelegationObject = { - recipientDID: - 'did:key:z4MXj1wBzi9jUstyP6ZRFhxGBEZDEnxMvBGB1DREJC4Q1cPgcmtYgETAQHDXiJTKzGHT7rPnZuMn2ofNjDJ3mUUWAYt8mzFfmR6DBGt21eESHyeNqiY7nzGGkcX6kjaR8LaGX2i29Qf75hFpEHsbrKGHd87UwLY9Ut3UfeU4Z7uhj2eTnSC1qDWM3vzyGT1tRoxXkcbviw1pwu6mNKcN1VJngW52x2y6L7hGRbTRr81vLiwCnTVxJ4kHTUHbW2u3ijA64iVEEdYr4yqqbfnDLp6F1PYuB6kfMX16Gkd7Dyw3GBSVSe3ba2NaitqPpqMC4iHSpNMPZfwFg1hBEeCTWWgDGA3xjyzYeFDWmc39fov7qXUTTWMgt', - deadline: 1753108795, - notBefore: 1753107795, - baseCapabilities: ['file/upload'], - fileCID: 'bafkreiahejox56s3d26zpojexilpwofks6yllrdfzye2tmuiu6kgzkuixi', - proof: process.env.STORACHA_PROOF, - storachaKey: process.env.STORACHA_KEY, - } - const result = await axios.post( - `${BASE_URL}/create-delegation`, - DelegationObject, - ) - // expect(typeof result.data.message).toBe(string); - expect(result.data.message).toBe('Delegation created successfully') -}) diff --git a/server/src/utils/compute-cid.ts b/server/src/utils/compute-cid.ts index 6197404..b5c31a6 100644 --- a/server/src/utils/compute-cid.ts +++ b/server/src/utils/compute-cid.ts @@ -1,42 +1,44 @@ -import { - CAREncoderStream, - createDirectoryEncoderStream, - createFileEncoderStream, -} from 'ipfs-car' +import { CAREncoderStream, createDirectoryEncoderStream } from 'ipfs-car' import { logger } from './logger.js' /** - * pre-computes the Storacha/IPFS-compatible CID for a file/directory - * - * This is necessary for us to ensure that a deposit is actually made before - * delegations to store data is provided. + * Pre-computes the IPFS-compatible CID for a file or directory. + * Always uses directory encoding so the CID matches what Pinata pins + * via fileArray — which is required for payment verification. */ export async function computeCID( fileMap: Record, ): Promise { try { - if (Object.keys(fileMap).length === 1) { - const [_, content] = Object.entries(fileMap)[0] - const file = new Blob([content]) - - let rootCID: any - - await createFileEncoderStream(file) - .pipeThrough( - new TransformStream({ - transform(block, controller) { - rootCID = block.cid - controller.enqueue(block) - }, - }), - ) - .pipeThrough(new CAREncoderStream()) - .pipeTo(new WritableStream()) - - return rootCID.toString() - } - - return await computeDirectoryCID(fileMap) + const files = Object.entries(fileMap).map(([name, content]) => ({ + name, + stream: () => + new ReadableStream({ + start(controller) { + controller.enqueue(content) + controller.close() + }, + }), + })) + + let rootCID: any + let blockCount = 0 + + await createDirectoryEncoderStream(files) + .pipeThrough( + new TransformStream({ + transform(block, controller) { + blockCount++ + rootCID = block.cid + controller.enqueue(block) + }, + }), + ) + .pipeThrough(new CAREncoderStream()) + .pipeTo(new WritableStream()) + + logger.info('CID computed', { blockCount, cid: rootCID?.toString() }) + return rootCID.toString() } catch (error) { logger.error('Error computing CID', { error: error instanceof Error ? error.message : String(error), @@ -46,49 +48,3 @@ export async function computeCID( ) } } - -/** - * Compute CID for directory (multiple files) - * This matches how Storacha handles directory uploads - */ -async function computeDirectoryCID( - fileMap: Record, -): Promise { - // need to "pack" the files into an ipfs-compatible format - const files = Object.entries(fileMap).map(([name, content]) => ({ - name, - // would've just passed the destructured `content` as is below instead of this - // but TS complains that ipfs-car's FileLike needs to be inferred - stream: () => - new ReadableStream({ - start(controller) { - controller.enqueue(content) - controller.close() - }, - }), - })) - - let rootCID: any - let blockCount = 0 - - await createDirectoryEncoderStream(files) - .pipeThrough( - new TransformStream({ - transform(block, controller) { - blockCount++ - // For directories, we want the final CID that represents the directory itself - // This is usually the last block, but we can also check if it's a directory type - rootCID = block.cid - controller.enqueue(block) - }, - }), - ) - .pipeThrough(new CAREncoderStream()) - .pipeTo(new WritableStream()) - - logger.info('Directory CID computed', { - blockCount, - rootCid: rootCID?.toString(), - }) - return rootCID.toString() -} diff --git a/server/src/utils/storacha.ts b/server/src/utils/pricing.ts similarity index 61% rename from server/src/utils/storacha.ts rename to server/src/utils/pricing.ts index d41f3ca..87888b4 100644 --- a/server/src/utils/storacha.ts +++ b/server/src/utils/pricing.ts @@ -1,8 +1,3 @@ -import * as Client from '@storacha/client' -import { create } from '@storacha/client' -import { Signer } from '@storacha/client/principal/ed25519' -import * as Proof from '@storacha/client/proof' -import { StoreMemory } from '@storacha/client/stores/memory' import { db } from '../db/db.js' import { configTable } from '../db/schema.js' import { getSolPrice } from '../services/price/sol-price.service.js' @@ -10,33 +5,6 @@ import { QuoteInput } from '../types.js' import { getAmountInLamportsFromUSD, getAmountInUSD } from './constant.js' import { logger } from './logger.js' -/** - * Initializes a Storacha client using user-provided key and proof. - * - * @returns {Promise} Initialized W3UP client - */ -export async function initStorachaClient(): Promise { - const principal = Signer.parse(process.env.STORACHA_KEY!) - const store = new StoreMemory() - const client = await create({ principal, store }) - - const proof = await Proof.parse(process.env.STORACHA_PROOF!) - const space = await client.addSpace(proof) - await client.setCurrentSpace(space.did()) - - // we had errors on the server because the proof for this delegation - // wasn't provided. the plan/get capability cannot be scoped to the server (space DID?) agent - // so the appropriate thing to do is to get the plan proof separately with sacap: https://github.com/kaf-lamed-beyt/sacap - // since it is an account-level capability (did:mailto...) so the - // plan limit call in the usage service doesn't error. - if (process.env.STORACHA_PLAN_PROOF) { - const planProof = await Proof.parse(process.env.STORACHA_PLAN_PROOF) - await client.addProof(planProof) - } - - return client -} - /** * This function returns the amount it will cost to upload the file, keep it for minimum duration */ diff --git a/ui/package.json b/ui/package.json index 170da22..09420c2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -28,8 +28,8 @@ "@tanstack/react-router": "^1.168.8", "@tanstack/react-router-devtools": "^1.166.11", "@tanstack/router-plugin": "^1.167.9", - "@toju.network/fil": "^0.2.2", - "@toju.network/sol": "^0.1.6", + "@toju.network/fil": "^1.0.0", + "@toju.network/sol": "^1.0.0", "dayjs": "^1.11.20", "embla-carousel-react": "^8.6.0", "framer-motion": "^11.18.2", diff --git a/ui/src/hooks/upload-history.ts b/ui/src/hooks/upload-history.ts index f0355c2..cea2fb9 100644 --- a/ui/src/hooks/upload-history.ts +++ b/ui/src/hooks/upload-history.ts @@ -73,10 +73,7 @@ export function useUploadHistory() { filename: deposit.fileName || 'Unknown File', size: Number(deposit.fileSize) || 0, type: deposit.fileType || 'application/octet-stream', - url: - deposit.fileType === 'directory' - ? `https://w3s.link/ipfs/${deposit.contentCid}` - : `https://w3s.link/ipfs/${deposit.contentCid}${deposit.fileName ? `/${deposit.fileName}` : ''}`, + url: deposit.url || '', uploadedAt: deposit.createdAt, signature: deposit.transactionHash || '',