@fil-b/foc-storage-mcp 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -163
- package/dist/mcp-server.js +1094 -429
- package/package.json +4 -2
package/dist/mcp-server.js
CHANGED
|
@@ -15,50 +15,51 @@ import { Agent } from "@mastra/core/agent";
|
|
|
15
15
|
// src/mastra/tools/dataset-tools.ts
|
|
16
16
|
import { createTool } from "@mastra/core";
|
|
17
17
|
|
|
18
|
-
// src/types/core.ts
|
|
19
|
-
import {
|
|
20
|
-
TIME_CONSTANTS,
|
|
21
|
-
SIZE_CONSTANTS,
|
|
22
|
-
TOKENS
|
|
23
|
-
} from "@filoz/synapse-sdk";
|
|
24
|
-
|
|
25
18
|
// src/types/schemas.ts
|
|
26
19
|
import { z as z2 } from "zod";
|
|
27
20
|
|
|
28
21
|
// src/config/index.ts
|
|
22
|
+
import { calibration, mainnet } from "@filoz/synapse-core/chains";
|
|
29
23
|
import { config } from "dotenv";
|
|
30
24
|
import { z } from "zod";
|
|
31
25
|
config();
|
|
32
26
|
var EnvSchema = z.object({
|
|
33
27
|
PRIVATE_KEY: z.string().min(1, "PRIVATE_KEY is required"),
|
|
34
28
|
FILECOIN_NETWORK: z.enum(["mainnet", "calibration"]).default("calibration"),
|
|
35
|
-
TOTAL_STORAGE_NEEDED_GiB: z.coerce.number().default(
|
|
36
|
-
PERSISTENCE_PERIOD_DAYS: z.coerce.number().default(365),
|
|
37
|
-
RUNOUT_NOTIFICATION_THRESHOLD_DAYS: z.coerce.number().default(
|
|
29
|
+
TOTAL_STORAGE_NEEDED_GiB: z.coerce.number().default(150),
|
|
30
|
+
PERSISTENCE_PERIOD_DAYS: z.coerce.number().default(365).refine((value) => value >= 30, { message: "PERSISTENCE_PERIOD_DAYS must be greater than or equal to 30" }),
|
|
31
|
+
RUNOUT_NOTIFICATION_THRESHOLD_DAYS: z.coerce.number().default(45).refine((value) => value >= 30, { message: "RUNOUT_NOTIFICATION_THRESHOLD_DAYS must be greater than or equal to 30" })
|
|
38
32
|
});
|
|
39
|
-
var env =
|
|
33
|
+
var env = {
|
|
34
|
+
...EnvSchema.parse(process.env),
|
|
35
|
+
...{
|
|
36
|
+
CDN_DATASET_FEE: 10n ** 18n
|
|
37
|
+
}
|
|
38
|
+
};
|
|
40
39
|
var NETWORK_CONFIGS = {
|
|
41
40
|
mainnet: {
|
|
42
41
|
chainId: 314,
|
|
43
42
|
name: "Filecoin Mainnet",
|
|
44
|
-
rpcUrl:
|
|
43
|
+
rpcUrl: mainnet.rpcUrls.default.http
|
|
45
44
|
},
|
|
46
45
|
calibration: {
|
|
47
46
|
chainId: 314159,
|
|
48
47
|
name: "Filecoin Calibration",
|
|
49
|
-
rpcUrl:
|
|
48
|
+
rpcUrl: calibration.rpcUrls.default.http
|
|
50
49
|
}
|
|
51
50
|
};
|
|
51
|
+
var CONTRACTS = env.FILECOIN_NETWORK === "calibration" ? calibration.contracts : mainnet.contracts;
|
|
52
52
|
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
53
53
|
var DATA_SET_CREATION_FEE = BigInt(0.1 * 10 ** 18);
|
|
54
54
|
var BYTES_PER_TIB = 1024n * 1024n * 1024n * 1024n;
|
|
55
55
|
var BYTES_PER_GIB = 1024n * 1024n * 1024n;
|
|
56
|
+
var CDN_EGRESS_RATE_PER_TIB = 7;
|
|
56
57
|
var DEFAULT_EXPECTED_STORAGE_BYTES = 1024 * 1024 * 1024 * 1024;
|
|
57
58
|
|
|
58
59
|
// src/types/schemas.ts
|
|
59
|
-
import { SIZE_CONSTANTS
|
|
60
|
+
import { SIZE_CONSTANTS } from "@filoz/synapse-core/utils";
|
|
60
61
|
var GetDatasetsSchema = z2.object({
|
|
61
|
-
includeAllDatasets: z2.boolean().optional().describe("Include all datasets. Default:
|
|
62
|
+
includeAllDatasets: z2.boolean().optional().default(true).describe("Include all datasets. Default: true"),
|
|
62
63
|
filterByCDN: z2.boolean().optional().describe("Filter to only CDN-enabled datasets. Default: false. If includeAllDatasets is true, this will be ignored")
|
|
63
64
|
});
|
|
64
65
|
var GetDatasetSchema = z2.object({
|
|
@@ -83,13 +84,16 @@ var UploadFileSchema = z2.object({
|
|
|
83
84
|
autoPayment: z2.boolean().optional().default(true).describe("Automatically process payment if insufficient balance. Default: true")
|
|
84
85
|
});
|
|
85
86
|
var GetBalancesSchema = z2.object({
|
|
86
|
-
storageCapacityBytes: z2.number().optional().default(env.TOTAL_STORAGE_NEEDED_GiB * Number(
|
|
87
|
+
storageCapacityBytes: z2.number().optional().default(env.TOTAL_STORAGE_NEEDED_GiB * Number(SIZE_CONSTANTS.GiB)).describe("Storage capacity in bytes. Default: 1 TB. This is used to calculate the storage needs and the deposit needed."),
|
|
87
88
|
persistencePeriodDays: z2.number().optional().default(env.PERSISTENCE_PERIOD_DAYS).describe("Persistence period in days. Default: 365. This is used to calculate the storage needs and the deposit needed."),
|
|
88
89
|
notificationThresholdDays: z2.number().optional().default(env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS).describe("Notification threshold in days. Default: 10. This is used check if the user needs to top up their storage balance before the storage balance runs out.")
|
|
89
90
|
});
|
|
90
91
|
var ProcessPaymentSchema = z2.object({
|
|
91
92
|
depositAmount: z2.number().optional().default(0).describe("Amount to deposit in USDFC. Default: 0. If not provided, the tool will check the balance and deposit the necessary amount.")
|
|
92
93
|
});
|
|
94
|
+
var ProcessWithdrawalSchema = z2.object({
|
|
95
|
+
withdrawalAmount: z2.number().optional().default(0).describe("Amount to withdraw in USDFC. Default: 0. If not provided, the tool will check the balance and withdraw the necessary amount.")
|
|
96
|
+
});
|
|
93
97
|
var GetProvidersSchema = z2.object({
|
|
94
98
|
onlyApproved: z2.boolean().optional().default(true).describe("Filter to only approved providers. Default: true")
|
|
95
99
|
});
|
|
@@ -121,7 +125,24 @@ var DataSetSchema = z2.object({
|
|
|
121
125
|
totalDatasetSizeMessage: z2.string(),
|
|
122
126
|
dataSetPieces: z2.array(DataSetPieceSchema)
|
|
123
127
|
});
|
|
124
|
-
var
|
|
128
|
+
var StorageBalanceResultSchema = z2.object({
|
|
129
|
+
filBalance: z2.bigint(),
|
|
130
|
+
usdfcBalance: z2.bigint(),
|
|
131
|
+
availableStorageFundsUsdfc: z2.bigint(),
|
|
132
|
+
depositNeeded: z2.bigint(),
|
|
133
|
+
availableToFreeUp: z2.bigint(),
|
|
134
|
+
daysLeftAtMaxBurnRate: z2.number(),
|
|
135
|
+
daysLeftAtBurnRate: z2.number(),
|
|
136
|
+
isRateSufficient: z2.boolean(),
|
|
137
|
+
isLockupSufficient: z2.boolean(),
|
|
138
|
+
isSufficient: z2.boolean(),
|
|
139
|
+
currentStorageMonthlyRate: z2.bigint(),
|
|
140
|
+
maxStorageMonthlyRate: z2.bigint()
|
|
141
|
+
});
|
|
142
|
+
var StorageBalanceResultSerializedSchema = z2.object({
|
|
143
|
+
filBalance: z2.string(),
|
|
144
|
+
usdfcBalance: z2.string(),
|
|
145
|
+
availableStorageFundsUsdfc: z2.string(),
|
|
125
146
|
depositNeeded: z2.string(),
|
|
126
147
|
availableToFreeUp: z2.string(),
|
|
127
148
|
daysLeftAtMaxBurnRate: z2.number(),
|
|
@@ -129,14 +150,27 @@ var FormattedStorageBalanceResultSchema = z2.object({
|
|
|
129
150
|
isRateSufficient: z2.boolean(),
|
|
130
151
|
isLockupSufficient: z2.boolean(),
|
|
131
152
|
isSufficient: z2.boolean(),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
currentStorageMonthlyRate: z2.string(),
|
|
154
|
+
maxStorageMonthlyRate: z2.string()
|
|
155
|
+
});
|
|
156
|
+
var FormattedStorageBalanceResultSchema = z2.object({
|
|
157
|
+
filBalance: z2.string(),
|
|
158
|
+
usdfcBalance: z2.string(),
|
|
159
|
+
availableStorageFundsUsdfc: z2.string(),
|
|
160
|
+
currentStorageMonthlyRate: z2.string(),
|
|
161
|
+
maxStorageMonthlyRate: z2.string(),
|
|
162
|
+
daysLeftAtMaxBurnRate: z2.string(),
|
|
163
|
+
daysLeftAtBurnRate: z2.string(),
|
|
164
|
+
depositNeeded: z2.string(),
|
|
165
|
+
availableToFreeUp: z2.string(),
|
|
166
|
+
isRateSufficient: z2.boolean(),
|
|
167
|
+
isLockupSufficient: z2.boolean(),
|
|
168
|
+
isSufficient: z2.boolean()
|
|
135
169
|
});
|
|
136
170
|
var GetProvidersOutputSchema = z2.object({
|
|
137
171
|
success: z2.boolean(),
|
|
138
172
|
// Success fields
|
|
139
|
-
providers: z2.array(z2.
|
|
173
|
+
providers: z2.array(z2.custom()),
|
|
140
174
|
count: z2.number().optional(),
|
|
141
175
|
// Error fields
|
|
142
176
|
error: z2.string().optional(),
|
|
@@ -166,14 +200,24 @@ var ProcessPaymentOutputSchema = z2.object({
|
|
|
166
200
|
deposit: z2.number()
|
|
167
201
|
}).optional(),
|
|
168
202
|
available: z2.number().optional(),
|
|
203
|
+
progressLog: z2.array(z2.string()).optional(),
|
|
204
|
+
// Error fields
|
|
205
|
+
error: z2.string().optional()
|
|
206
|
+
});
|
|
207
|
+
var ProcessWithdrawalOutputSchema = z2.object({
|
|
208
|
+
success: z2.boolean(),
|
|
209
|
+
message: z2.string(),
|
|
210
|
+
txHash: z2.string().nullable().optional(),
|
|
211
|
+
progressLog: z2.array(z2.string()).optional(),
|
|
169
212
|
// Error fields
|
|
170
213
|
error: z2.string().optional()
|
|
171
214
|
});
|
|
172
215
|
var GetBalancesOutputSchema = z2.object({
|
|
173
216
|
success: z2.boolean(),
|
|
174
217
|
// Success fields
|
|
175
|
-
checkStorageBalanceResultFormatted:
|
|
176
|
-
checkStorageBalanceResult:
|
|
218
|
+
checkStorageBalanceResultFormatted: FormattedStorageBalanceResultSchema.optional(),
|
|
219
|
+
checkStorageBalanceResult: StorageBalanceResultSerializedSchema.optional(),
|
|
220
|
+
progressLog: z2.array(z2.string()).optional(),
|
|
177
221
|
// Error fields
|
|
178
222
|
error: z2.string().optional(),
|
|
179
223
|
message: z2.string().optional()
|
|
@@ -181,8 +225,9 @@ var GetBalancesOutputSchema = z2.object({
|
|
|
181
225
|
var GetDatasetsOutputSchema = z2.object({
|
|
182
226
|
success: z2.boolean(),
|
|
183
227
|
// Success fields
|
|
184
|
-
datasets: z2.array(
|
|
228
|
+
datasets: z2.array(z2.custom()).optional(),
|
|
185
229
|
count: z2.number().optional(),
|
|
230
|
+
progressLog: z2.array(z2.string()).optional(),
|
|
186
231
|
// Error fields
|
|
187
232
|
error: z2.string().optional(),
|
|
188
233
|
// Common field
|
|
@@ -191,7 +236,7 @@ var GetDatasetsOutputSchema = z2.object({
|
|
|
191
236
|
var GetDatasetOutputSchema = z2.object({
|
|
192
237
|
success: z2.boolean(),
|
|
193
238
|
// Success fields
|
|
194
|
-
dataset:
|
|
239
|
+
dataset: z2.custom().optional(),
|
|
195
240
|
// Error fields
|
|
196
241
|
error: z2.string().optional(),
|
|
197
242
|
// Common field
|
|
@@ -200,18 +245,19 @@ var GetDatasetOutputSchema = z2.object({
|
|
|
200
245
|
var CreateDatasetOutputSchema = z2.object({
|
|
201
246
|
success: z2.boolean(),
|
|
202
247
|
// Success fields
|
|
203
|
-
datasetId: z2.string().optional(),
|
|
204
|
-
txHash: z2.string().optional(),
|
|
248
|
+
datasetId: z2.string().nullable().optional(),
|
|
249
|
+
txHash: z2.string().nullable().optional(),
|
|
250
|
+
progressLog: z2.array(z2.string()).optional(),
|
|
205
251
|
// Error fields
|
|
206
|
-
error: z2.string().optional(),
|
|
252
|
+
error: z2.string().nullable().optional(),
|
|
207
253
|
// Common field
|
|
208
254
|
message: z2.string()
|
|
209
255
|
});
|
|
210
256
|
|
|
211
257
|
// src/lib/calculations.ts
|
|
212
258
|
import Decimal from "decimal.js";
|
|
213
|
-
import {
|
|
214
|
-
import {
|
|
259
|
+
import { getSize } from "@filoz/synapse-core/piece";
|
|
260
|
+
import { SIZE_CONSTANTS as SIZE_CONSTANTS2 } from "@filoz/synapse-core/utils";
|
|
215
261
|
Decimal.set({
|
|
216
262
|
precision: 34,
|
|
217
263
|
rounding: Decimal.ROUND_HALF_UP,
|
|
@@ -221,31 +267,6 @@ Decimal.set({
|
|
|
221
267
|
minE: -9e15,
|
|
222
268
|
modulo: Decimal.ROUND_DOWN
|
|
223
269
|
});
|
|
224
|
-
var toDecimal = (value) => value instanceof Decimal ? value : new Decimal(value.toString());
|
|
225
|
-
var bytesToKiB = (bytes) => toDecimal(bytes).div(new Decimal(SIZE_CONSTANTS3.KiB.toString()));
|
|
226
|
-
var bytesToMiB = (bytes) => toDecimal(bytes).div(new Decimal(SIZE_CONSTANTS3.MiB.toString()));
|
|
227
|
-
var bytesToGiB = (bytes) => toDecimal(bytes).div(new Decimal(SIZE_CONSTANTS3.GiB.toString()));
|
|
228
|
-
var getPieceInfoFromCidBytes = (input) => {
|
|
229
|
-
const sizeBytes = BigInt(getSizeFromPieceCID(input));
|
|
230
|
-
return {
|
|
231
|
-
sizeBytes,
|
|
232
|
-
sizeKiB: bytesToKiB(sizeBytes).toNumber(),
|
|
233
|
-
sizeMiB: bytesToMiB(sizeBytes).toNumber(),
|
|
234
|
-
sizeGiB: bytesToGiB(sizeBytes).toNumber()
|
|
235
|
-
};
|
|
236
|
-
};
|
|
237
|
-
var sizeInfoMessage = (sizeInfo) => {
|
|
238
|
-
if (sizeInfo.sizeInGB > 0.1) {
|
|
239
|
-
return `Dataset size: ${sizeInfo.sizeInGB.toFixed(4)} GB`;
|
|
240
|
-
}
|
|
241
|
-
if (sizeInfo.sizeInMiB > 0.1) {
|
|
242
|
-
return `Dataset size: ${sizeInfo.sizeInMiB.toFixed(4)} MB`;
|
|
243
|
-
}
|
|
244
|
-
if (sizeInfo.sizeInKiB > 0.1) {
|
|
245
|
-
return `Dataset size: ${sizeInfo.sizeInKiB.toFixed(4)} KB`;
|
|
246
|
-
}
|
|
247
|
-
return `Dataset size: ${sizeInfo.sizeInBytes} Bytes`;
|
|
248
|
-
};
|
|
249
270
|
|
|
250
271
|
// src/lib/synapse.ts
|
|
251
272
|
import { Synapse } from "@filoz/synapse-sdk";
|
|
@@ -254,7 +275,7 @@ var getSynapseInstance = async () => {
|
|
|
254
275
|
const synapse = await Synapse.create(
|
|
255
276
|
{
|
|
256
277
|
privateKey: env.PRIVATE_KEY,
|
|
257
|
-
rpcURL: NETWORK_CONFIGS[network].rpcUrl
|
|
278
|
+
rpcURL: NETWORK_CONFIGS[network].rpcUrl[0]
|
|
258
279
|
}
|
|
259
280
|
);
|
|
260
281
|
return synapse;
|
|
@@ -284,6 +305,11 @@ function createErrorResponse(errorType, message, additionalData) {
|
|
|
284
305
|
...additionalData
|
|
285
306
|
};
|
|
286
307
|
}
|
|
308
|
+
function toBaseUnits(amount, decimals = 18) {
|
|
309
|
+
const [whole, decimal = ""] = amount.split(".");
|
|
310
|
+
const paddedDecimal = decimal.padEnd(decimals, "0").slice(0, decimals);
|
|
311
|
+
return BigInt(whole + paddedDecimal);
|
|
312
|
+
}
|
|
287
313
|
function fromBaseUnits(amount, decimals = 18) {
|
|
288
314
|
const str = amount.toString().padStart(decimals + 1, "0");
|
|
289
315
|
const whole = str.slice(0, -decimals) || "0";
|
|
@@ -307,182 +333,223 @@ function serializeBigInt(obj) {
|
|
|
307
333
|
}
|
|
308
334
|
return obj;
|
|
309
335
|
}
|
|
336
|
+
function getExplorerUrl(txHash) {
|
|
337
|
+
const isMainnet = env.FILECOIN_NETWORK === "mainnet";
|
|
338
|
+
const baseUrl = isMainnet ? "https://filecoin.blockscout.com/tx" : "https://filecoin-testnet.blockscout.com/tx";
|
|
339
|
+
return `${baseUrl}/${txHash}`;
|
|
340
|
+
}
|
|
310
341
|
|
|
311
|
-
// src/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
epochs
|
|
335
|
-
);
|
|
336
|
-
const receipt = await tx.wait(1);
|
|
337
|
-
return { txHash: receipt?.hash || tx.hash, success: true };
|
|
342
|
+
// src/lib/errors.ts
|
|
343
|
+
function getTopUpMessage(isTestnet) {
|
|
344
|
+
if (isTestnet) {
|
|
345
|
+
return `
|
|
346
|
+
To resolve insufficient balance errors:
|
|
347
|
+
" For tFIL: Visit https://faucet.calibration.fildev.network/
|
|
348
|
+
" For tUSDFC: Visit https://forest-explorer.chainsafe.dev/faucet/calibnet_usdfc
|
|
349
|
+
`;
|
|
350
|
+
}
|
|
351
|
+
return `
|
|
352
|
+
To resolve insufficient balance errors:
|
|
353
|
+
" Top up your FIL or USDFC balance
|
|
354
|
+
" Ensure you have sufficient funds for the operation
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
function synapseErrorHandler(error) {
|
|
358
|
+
const errorParts = ["Payment processing failed"];
|
|
359
|
+
if (error?.cause) {
|
|
360
|
+
const cause = typeof error.cause === "string" ? error.cause : JSON.stringify(error.cause);
|
|
361
|
+
errorParts.push(`Cause: ${cause}`);
|
|
362
|
+
}
|
|
363
|
+
if (error?.shortMessage) {
|
|
364
|
+
errorParts.push(`Message: ${error.shortMessage}`);
|
|
338
365
|
}
|
|
366
|
+
if (error?.details) {
|
|
367
|
+
errorParts.push(`Details: ${error.details}`);
|
|
368
|
+
}
|
|
369
|
+
const isTestnet = env.FILECOIN_NETWORK === "calibration";
|
|
370
|
+
errorParts.push(getTopUpMessage(isTestnet));
|
|
371
|
+
return errorParts.join("\n");
|
|
339
372
|
}
|
|
340
373
|
|
|
341
374
|
// src/services/dataset-service.ts
|
|
342
|
-
import {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
375
|
+
import { metadataArrayToObject } from "@filoz/synapse-core/utils";
|
|
376
|
+
|
|
377
|
+
// src/services/viem.ts
|
|
378
|
+
import { createPublicClient, createWalletClient, http } from "viem";
|
|
379
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
380
|
+
import { calibration as calibration2, mainnet as mainnet2 } from "@filoz/synapse-core/chains";
|
|
381
|
+
var account = privateKeyToAccount(env.PRIVATE_KEY);
|
|
382
|
+
var chain = env.FILECOIN_NETWORK === "calibration" ? calibration2 : mainnet2;
|
|
383
|
+
var client = createWalletClient({
|
|
384
|
+
account,
|
|
385
|
+
chain,
|
|
386
|
+
transport: http()
|
|
387
|
+
});
|
|
388
|
+
var publicClient = createPublicClient({
|
|
389
|
+
chain,
|
|
390
|
+
transport: http()
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// src/services/dataset-service.ts
|
|
394
|
+
import { createDataSet, getDataSet, getDataSets, getPieces } from "@filoz/synapse-core/warm-storage";
|
|
395
|
+
import * as SP from "@filoz/synapse-core/sp";
|
|
396
|
+
import { readContract } from "viem/actions";
|
|
397
|
+
var getPiecesWithMetadata = async (dataSetId, pieces) => {
|
|
398
|
+
return await Promise.all(
|
|
399
|
+
pieces.map(async (piece) => {
|
|
400
|
+
const metadata = await readContract(client, {
|
|
401
|
+
address: CONTRACTS.storageView.address,
|
|
402
|
+
abi: CONTRACTS.storageView.abi,
|
|
403
|
+
functionName: "getAllPieceMetadata",
|
|
404
|
+
args: [dataSetId, BigInt(piece.id)]
|
|
405
|
+
});
|
|
350
406
|
return {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
count: 0,
|
|
354
|
-
message: "No datasets found. Upload files to create your first dataset."
|
|
407
|
+
...piece,
|
|
408
|
+
metadata: metadataArrayToObject(metadata)
|
|
355
409
|
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (includeAll) {
|
|
368
|
-
return true;
|
|
369
|
-
}
|
|
370
|
-
return dataset.withCDN === withCDN;
|
|
410
|
+
})
|
|
411
|
+
);
|
|
412
|
+
};
|
|
413
|
+
var getDatasetsService = async (withCDN = false, includeAll = false) => {
|
|
414
|
+
const dataSets = (await getDataSets(publicClient, {
|
|
415
|
+
address: account.address
|
|
416
|
+
})).filter((dataset) => includeAll || withCDN && dataset.cdn);
|
|
417
|
+
const datasetsWithPieces = await Promise.all(dataSets.map(async (dataset) => {
|
|
418
|
+
const pieces = await getPieces(publicClient, {
|
|
419
|
+
address: account.address,
|
|
420
|
+
dataSet: dataset
|
|
371
421
|
});
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return acc;
|
|
402
|
-
}, {});
|
|
403
|
-
const getRetrievalUrl = (pieceCid) => {
|
|
404
|
-
if (dataset.withCDN) {
|
|
405
|
-
return `https://${userAddress}.calibration.filbeam.io/${pieceCid}`;
|
|
406
|
-
} else {
|
|
407
|
-
const endsWithSlash = serviceURL.endsWith("/");
|
|
408
|
-
const serviceURLWithoutSlash = endsWithSlash ? serviceURL.slice(0, -1) : serviceURL;
|
|
409
|
-
return `${serviceURLWithoutSlash}/piece/${pieceCid}`;
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
const datasetSizeInfo = data.pieces.reduce((acc, piece) => {
|
|
413
|
-
acc.sizeInBytes += Number(pieces[piece.pieceCid.toV1().toString()].sizeBytes);
|
|
414
|
-
acc.sizeInKiB += Number(pieces[piece.pieceCid.toV1().toString()].sizeKiB);
|
|
415
|
-
acc.sizeInMiB += Number(pieces[piece.pieceCid.toV1().toString()].sizeMiB);
|
|
416
|
-
acc.sizeInGB += Number(pieces[piece.pieceCid.toV1().toString()].sizeGiB);
|
|
417
|
-
return acc;
|
|
418
|
-
}, { sizeInBytes: 0, sizeInKiB: 0, sizeInMiB: 0, sizeInGB: 0, message: "" });
|
|
419
|
-
const dataSetPieces = data.pieces.map((piece) => ({
|
|
420
|
-
pieceCid: piece.pieceCid.toV1().toString(),
|
|
421
|
-
retrievalUrl: getRetrievalUrl(piece.pieceCid.toV1().toString()),
|
|
422
|
-
sizes: pieces[piece.pieceCid.toV1().toString()],
|
|
423
|
-
metadata: piecesMetadata[piece.pieceId]
|
|
424
|
-
}));
|
|
425
|
-
return {
|
|
426
|
-
datasetId: dataset.pdpVerifierDataSetId,
|
|
427
|
-
withCDN: dataset.withCDN,
|
|
428
|
-
datasetMetadata: dataset.metadata,
|
|
429
|
-
totalDatasetSizeMessage: sizeInfoMessage(datasetSizeInfo),
|
|
430
|
-
dataSetPieces
|
|
431
|
-
};
|
|
432
|
-
} catch (error) {
|
|
433
|
-
return null;
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
);
|
|
422
|
+
return {
|
|
423
|
+
...dataset,
|
|
424
|
+
pieces: await getPiecesWithMetadata(dataset.dataSetId, pieces.pieces)
|
|
425
|
+
};
|
|
426
|
+
}));
|
|
427
|
+
return datasetsWithPieces;
|
|
428
|
+
};
|
|
429
|
+
var getDataSetService = async (datasetId) => {
|
|
430
|
+
const dataset = await getDataSet(publicClient, {
|
|
431
|
+
dataSetId: BigInt(datasetId)
|
|
432
|
+
});
|
|
433
|
+
const pieces = await getPieces(publicClient, {
|
|
434
|
+
address: account.address,
|
|
435
|
+
dataSet: dataset
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
...dataset,
|
|
439
|
+
pieces: await getPiecesWithMetadata(dataset.dataSetId, pieces.pieces)
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
var createDataSetService = async (provider, cdn, metadata) => {
|
|
443
|
+
try {
|
|
444
|
+
const dataset = await createDataSet(client, {
|
|
445
|
+
cdn,
|
|
446
|
+
payee: provider.payee,
|
|
447
|
+
endpoint: provider.pdp.serviceURL,
|
|
448
|
+
metadata
|
|
449
|
+
});
|
|
450
|
+
const { dataSetId } = await SP.pollForDataSetCreationStatus({ statusUrl: dataset.statusUrl });
|
|
437
451
|
return {
|
|
438
452
|
success: true,
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
message:
|
|
453
|
+
dataSetId: dataSetId.toString(),
|
|
454
|
+
txHash: dataset.txHash,
|
|
455
|
+
message: "Dataset created successfully"
|
|
442
456
|
};
|
|
443
457
|
} catch (error) {
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
458
|
+
return {
|
|
459
|
+
success: false,
|
|
460
|
+
txHash: null,
|
|
461
|
+
dataSetId: null,
|
|
462
|
+
message: "Failed to create dataset: " + synapseErrorHandler(error)
|
|
463
|
+
};
|
|
449
464
|
}
|
|
450
465
|
};
|
|
451
466
|
|
|
467
|
+
// src/services/payment-service.ts
|
|
468
|
+
import * as Payments from "@filoz/synapse-core/pay";
|
|
469
|
+
import { waitForTransactionReceipt } from "viem/actions";
|
|
470
|
+
async function processPaymentService(depositAmount) {
|
|
471
|
+
try {
|
|
472
|
+
const hash = await Payments.depositAndApprove(client, {
|
|
473
|
+
amount: BigInt(depositAmount)
|
|
474
|
+
});
|
|
475
|
+
const receipt = await waitForTransactionReceipt(client, {
|
|
476
|
+
hash
|
|
477
|
+
});
|
|
478
|
+
return { txHash: hash, success: receipt.status === "success" };
|
|
479
|
+
} catch (error) {
|
|
480
|
+
return {
|
|
481
|
+
txHash: null,
|
|
482
|
+
success: false,
|
|
483
|
+
error: synapseErrorHandler(error)
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async function processWithdrawalService(withdrawalAmount) {
|
|
488
|
+
try {
|
|
489
|
+
const hash = await Payments.withdraw(client, {
|
|
490
|
+
amount: BigInt(withdrawalAmount)
|
|
491
|
+
});
|
|
492
|
+
const receipt = await waitForTransactionReceipt(client, {
|
|
493
|
+
hash
|
|
494
|
+
});
|
|
495
|
+
return { txHash: hash, success: receipt.status === "success" };
|
|
496
|
+
} catch (error) {
|
|
497
|
+
return {
|
|
498
|
+
txHash: null,
|
|
499
|
+
success: false,
|
|
500
|
+
error: synapseErrorHandler(error)
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
452
505
|
// src/services/storage-service.ts
|
|
453
506
|
import {
|
|
454
|
-
SIZE_CONSTANTS as
|
|
455
|
-
TIME_CONSTANTS
|
|
456
|
-
|
|
457
|
-
WarmStorageService as WarmStorageService2
|
|
458
|
-
} from "@filoz/synapse-sdk";
|
|
507
|
+
SIZE_CONSTANTS as SIZE_CONSTANTS3,
|
|
508
|
+
TIME_CONSTANTS
|
|
509
|
+
} from "@filoz/synapse-core/utils";
|
|
459
510
|
import { formatUnits } from "viem";
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
511
|
+
import * as ERC20 from "@filoz/synapse-core/erc20";
|
|
512
|
+
import * as Payments2 from "@filoz/synapse-core/pay";
|
|
513
|
+
import { getBalance } from "viem/actions";
|
|
514
|
+
import * as WarmStorage from "@filoz/synapse-core/warm-storage";
|
|
515
|
+
var calculateStorageCost = (prices, sizeInBytes) => {
|
|
516
|
+
const { pricePerTiBPerMonthNoCDN, epochsPerMonth } = prices;
|
|
517
|
+
const sizeInBytesBigint = BigInt(sizeInBytes);
|
|
518
|
+
const perEpoch = pricePerTiBPerMonthNoCDN * sizeInBytesBigint / (SIZE_CONSTANTS3.TiB * epochsPerMonth);
|
|
519
|
+
const perDay = perEpoch * TIME_CONSTANTS.EPOCHS_PER_DAY;
|
|
520
|
+
const perMonth = perEpoch * epochsPerMonth;
|
|
521
|
+
return {
|
|
522
|
+
perEpoch,
|
|
523
|
+
perDay,
|
|
524
|
+
perMonth
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
var checkStorageBalance = async (storageCapacityBytes = env.TOTAL_STORAGE_NEEDED_GiB * Number(SIZE_CONSTANTS3.GiB), persistencePeriodDays = env.PERSISTENCE_PERIOD_DAYS) => {
|
|
528
|
+
const [filRaw, { value: usdfcRaw }, { availableFunds }, prices, operatorApprovals2] = await Promise.all([
|
|
529
|
+
getBalance(client, {
|
|
530
|
+
address: account.address
|
|
531
|
+
}),
|
|
532
|
+
ERC20.balance(client, {
|
|
533
|
+
address: account.address
|
|
534
|
+
}),
|
|
535
|
+
Payments2.accountInfo(client, {
|
|
536
|
+
address: account.address
|
|
537
|
+
}),
|
|
538
|
+
WarmStorage.servicePrice(client),
|
|
539
|
+
Payments2.operatorApprovals(client, {
|
|
540
|
+
address: account.address
|
|
541
|
+
})
|
|
466
542
|
]);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
} catch (error) {
|
|
471
|
-
console.error(error);
|
|
472
|
-
throw new Error("Error fetching wallet balances. \n FIL balance not available. \n The main cause of this error is that your wallet doesn't have any FIL and has no transaction history on the network. Action: Top up your wallet with FIL.");
|
|
473
|
-
}
|
|
474
|
-
let usdfcRaw = await synapse.payments.walletBalance(TOKENS2.USDFC);
|
|
475
|
-
const allowance = storageInfo.allowances;
|
|
476
|
-
const availableFunds = accountInfo.availableFunds;
|
|
477
|
-
const currentMonthlyRate = allowance.rateUsed * TIME_CONSTANTS3.EPOCHS_PER_MONTH;
|
|
478
|
-
const maxMonthlyRate = prices.perMonth;
|
|
543
|
+
const storageCosts = calculateStorageCost(prices, storageCapacityBytes);
|
|
544
|
+
const currentMonthlyRate = operatorApprovals2.rateUsed * TIME_CONSTANTS.EPOCHS_PER_MONTH;
|
|
545
|
+
const maxMonthlyRate = storageCosts.perMonth;
|
|
479
546
|
const daysLeftAtMaxBurnRate = maxMonthlyRate === 0n ? Infinity : Number(availableFunds) / Number(maxMonthlyRate) * 30;
|
|
480
547
|
const daysLeftAtBurnRate = currentMonthlyRate === 0n ? Infinity : Number(availableFunds) / Number(currentMonthlyRate) * 30;
|
|
481
|
-
const amountNeeded =
|
|
482
|
-
const totalDepositNeeded = daysLeftAtMaxBurnRate >= env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS ? 0n : amountNeeded -
|
|
483
|
-
const availableToFreeUp =
|
|
484
|
-
const isRateSufficient =
|
|
485
|
-
const isLockupSufficient =
|
|
548
|
+
const amountNeeded = storageCosts.perDay * BigInt(persistencePeriodDays);
|
|
549
|
+
const totalDepositNeeded = daysLeftAtMaxBurnRate >= env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS ? 0n : amountNeeded - availableFunds;
|
|
550
|
+
const availableToFreeUp = availableFunds > amountNeeded ? availableFunds - amountNeeded : 0n;
|
|
551
|
+
const isRateSufficient = operatorApprovals2.rateAllowance === MAX_UINT256;
|
|
552
|
+
const isLockupSufficient = operatorApprovals2.lockupAllowance === MAX_UINT256;
|
|
486
553
|
const isSufficient = isRateSufficient && isLockupSufficient && daysLeftAtMaxBurnRate >= env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS;
|
|
487
554
|
return {
|
|
488
555
|
filBalance: filRaw,
|
|
@@ -515,8 +582,8 @@ var formatStorageBalanceResult = (checkStorageBalanceResult) => {
|
|
|
515
582
|
isSufficient: checkStorageBalanceResult.isSufficient
|
|
516
583
|
};
|
|
517
584
|
};
|
|
518
|
-
var formatBalance = (
|
|
519
|
-
return `${Number(Number(formatUnits(
|
|
585
|
+
var formatBalance = (balance2, ticker) => {
|
|
586
|
+
return `${Number(Number(formatUnits(balance2, 18)).toFixed(8))} ${ticker}`;
|
|
520
587
|
};
|
|
521
588
|
var formatTime = (days) => {
|
|
522
589
|
if (days === Infinity) {
|
|
@@ -533,17 +600,89 @@ var formatTime = (days) => {
|
|
|
533
600
|
}
|
|
534
601
|
return `${Math.fround(days / 365)} years`;
|
|
535
602
|
};
|
|
603
|
+
var estimateStorageCost = async (sizeInBytes, durationInMonths, createCDNDataset = false) => {
|
|
604
|
+
const prices = await WarmStorage.servicePrice(client);
|
|
605
|
+
const { minimumPricePerMonth, pricePerTiBPerMonthNoCDN } = prices;
|
|
606
|
+
let pricePerMonth = pricePerTiBPerMonthNoCDN * BigInt(sizeInBytes) / BigInt(SIZE_CONSTANTS3.TiB);
|
|
607
|
+
const appliedMinimum = pricePerMonth < minimumPricePerMonth;
|
|
608
|
+
if (appliedMinimum) {
|
|
609
|
+
pricePerMonth = minimumPricePerMonth;
|
|
610
|
+
}
|
|
611
|
+
let totalCost = pricePerMonth * BigInt(durationInMonths);
|
|
612
|
+
const cdnSetupCost = createCDNDataset ? 10n ** 18n : 0n;
|
|
613
|
+
totalCost += cdnSetupCost;
|
|
614
|
+
const sizeInGiB = Number(sizeInBytes) / Number(SIZE_CONSTANTS3.GiB);
|
|
615
|
+
const sizeInTiB = Number(sizeInBytes) / Number(SIZE_CONSTANTS3.TiB);
|
|
616
|
+
let sizeFormatted;
|
|
617
|
+
if (sizeInTiB >= 1) {
|
|
618
|
+
sizeFormatted = `${sizeInTiB.toFixed(2)} TiB`;
|
|
619
|
+
} else if (sizeInGiB >= 1) {
|
|
620
|
+
sizeFormatted = `${sizeInGiB.toFixed(2)} GiB`;
|
|
621
|
+
} else {
|
|
622
|
+
const sizeInMiB = Number(sizeInBytes) / Number(SIZE_CONSTANTS3.MiB);
|
|
623
|
+
sizeFormatted = `${sizeInMiB.toFixed(2)} MiB`;
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
monthlyCost: pricePerMonth,
|
|
627
|
+
totalCost,
|
|
628
|
+
totalCostFormatted: formatUnits(totalCost, 18),
|
|
629
|
+
cdnSetupCost,
|
|
630
|
+
cdnSetupCostFormatted: formatUnits(cdnSetupCost, 18),
|
|
631
|
+
details: {
|
|
632
|
+
sizeInBytes,
|
|
633
|
+
sizeFormatted,
|
|
634
|
+
durationInMonths,
|
|
635
|
+
pricePerTiBPerMonth: pricePerTiBPerMonthNoCDN,
|
|
636
|
+
minimumPricePerMonth,
|
|
637
|
+
appliedMinimum
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/services/provider-service.ts
|
|
643
|
+
import { readProviders, getProvider as readProvider } from "@filoz/synapse-core/warm-storage";
|
|
644
|
+
var getProvider = async (providerId) => {
|
|
645
|
+
const provider = await readProvider(publicClient, {
|
|
646
|
+
providerId: BigInt(providerId)
|
|
647
|
+
});
|
|
648
|
+
return serializeBigInt(provider);
|
|
649
|
+
};
|
|
536
650
|
|
|
537
651
|
// src/mastra/tools/dataset-tools.ts
|
|
538
|
-
var
|
|
652
|
+
var getDatasets = createTool({
|
|
539
653
|
id: "getDatasets",
|
|
540
654
|
description: "Retrieve all datasets owned by the connected wallet with comprehensive information including piece CIDs, file sizes, provider details, and retrieval URLs. Filter by CDN status or view all datasets. Each dataset contains complete metadata about stored files and their blockchain storage proofs. Use this to inventory files, check storage status, or locate specific uploads.",
|
|
541
655
|
inputSchema: GetDatasetsSchema,
|
|
542
656
|
outputSchema: GetDatasetsOutputSchema,
|
|
543
657
|
execute: async ({ context }) => {
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
|
|
658
|
+
const progressLog = [];
|
|
659
|
+
const log = (msg) => {
|
|
660
|
+
progressLog.push(msg);
|
|
661
|
+
};
|
|
662
|
+
try {
|
|
663
|
+
const withCDN = context.filterByCDN ?? false;
|
|
664
|
+
const includeAll = context.includeAllDatasets ?? false;
|
|
665
|
+
log("Fetching datasets from blockchain...");
|
|
666
|
+
const dataSets = await getDatasetsService(withCDN, includeAll);
|
|
667
|
+
if (dataSets.length > 0) {
|
|
668
|
+
log(`Retrieved ${dataSets.length} dataset(s)`);
|
|
669
|
+
log("Processing dataset metadata...");
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
success: true,
|
|
673
|
+
datasets: dataSets.map((dataset) => serializeBigInt(dataset)),
|
|
674
|
+
count: dataSets.length,
|
|
675
|
+
message: `Found ${dataSets.length} dataset(s)`,
|
|
676
|
+
progressLog
|
|
677
|
+
};
|
|
678
|
+
} catch (error) {
|
|
679
|
+
return {
|
|
680
|
+
success: false,
|
|
681
|
+
error: error.message,
|
|
682
|
+
message: `Failed to fetch datasets: ${error.message}`,
|
|
683
|
+
progressLog
|
|
684
|
+
};
|
|
685
|
+
}
|
|
547
686
|
}
|
|
548
687
|
});
|
|
549
688
|
var getDataset = createTool({
|
|
@@ -553,11 +692,11 @@ var getDataset = createTool({
|
|
|
553
692
|
outputSchema: GetDatasetOutputSchema,
|
|
554
693
|
execute: async ({ context }) => {
|
|
555
694
|
try {
|
|
556
|
-
const dataset = await
|
|
695
|
+
const dataset = await getDataSetService(Number(context.datasetId));
|
|
557
696
|
return {
|
|
558
|
-
success:
|
|
559
|
-
dataset: serializeBigInt(dataset
|
|
560
|
-
message:
|
|
697
|
+
success: true,
|
|
698
|
+
dataset: serializeBigInt(dataset),
|
|
699
|
+
message: "Dataset fetched successfully"
|
|
561
700
|
};
|
|
562
701
|
} catch (error) {
|
|
563
702
|
return createErrorResponse(
|
|
@@ -570,60 +709,60 @@ var getDataset = createTool({
|
|
|
570
709
|
});
|
|
571
710
|
var createDataset = createTool({
|
|
572
711
|
id: "createDataset",
|
|
573
|
-
description: "Create a new dataset container on Filecoin for organizing related files with consistent storage settings. Datasets define storage parameters (CDN enabled/disabled, provider selection) that apply to all files added to them. Creating datasets upfront allows for better file organization and consistent retrieval performance.
|
|
712
|
+
description: "Create a new dataset container on Filecoin for organizing related files with consistent storage settings. Datasets define storage parameters (CDN enabled/disabled, provider selection) that apply to all files added to them. Creating datasets upfront allows for better file organization and consistent retrieval performance. Provider ID is required - use getProviders to list available providers. Payment (1 USDFC) is processed automatically for CDN-enabled datasets. Returns dataset ID, transaction hash, and progress tracking through validation, payment, and creation steps.",
|
|
574
713
|
inputSchema: CreateDatasetSchema,
|
|
575
714
|
outputSchema: CreateDatasetOutputSchema,
|
|
576
715
|
execute: async ({ context }) => {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
716
|
+
const progressLog = [];
|
|
717
|
+
const log = (msg) => {
|
|
718
|
+
progressLog.push(msg);
|
|
719
|
+
};
|
|
720
|
+
if (!context.providerId) {
|
|
721
|
+
return {
|
|
722
|
+
success: false,
|
|
723
|
+
error: "provider_id_required",
|
|
724
|
+
message: "Provider ID is required. Use getProviders tool to list available providers and select one by ID.",
|
|
725
|
+
progressLog
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
log("Validating provider ID...");
|
|
729
|
+
const provider = await getProvider(Number(context.providerId));
|
|
730
|
+
log(`Provider validated (ID: ${context.providerId})`);
|
|
731
|
+
const withCDN = context.withCDN ?? false;
|
|
732
|
+
if (withCDN) {
|
|
733
|
+
log("Processing CDN payment (1 USDFC)...");
|
|
734
|
+
const { success, error } = await processPaymentService(BigInt(env.CDN_DATASET_FEE));
|
|
735
|
+
if (!success) {
|
|
736
|
+
return {
|
|
737
|
+
success: false,
|
|
738
|
+
error: "payment_failed",
|
|
739
|
+
message: `Failed to process CDN payment (1 USDFC required): ${error}. Use processPayment tool to add funds first.`,
|
|
740
|
+
progressLog
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
log("CDN payment processed successfully");
|
|
744
|
+
}
|
|
745
|
+
log("Creating dataset on blockchain...");
|
|
746
|
+
const result = await createDataSetService(provider, withCDN, context.metadata ?? {});
|
|
747
|
+
if (result.success) {
|
|
748
|
+
log("Dataset created successfully");
|
|
749
|
+
if (result.txHash) {
|
|
750
|
+
log(`View transaction: ${getExplorerUrl(result.txHash)}`);
|
|
589
751
|
}
|
|
590
|
-
let datasetId;
|
|
591
|
-
let txHash;
|
|
592
|
-
await synapse.createStorage({
|
|
593
|
-
providerId: context.providerId ? parseInt(context.providerId) : void 0,
|
|
594
|
-
forceCreateDataSet: true,
|
|
595
|
-
metadata: context.metadata,
|
|
596
|
-
callbacks: {
|
|
597
|
-
onDataSetCreationStarted: (txResponse) => {
|
|
598
|
-
txHash = txResponse.hash;
|
|
599
|
-
console.log(`[Dataset] Creation started (tx: ${txResponse.hash})`);
|
|
600
|
-
},
|
|
601
|
-
onDataSetCreationProgress: (status) => {
|
|
602
|
-
if (status.serverConfirmed) {
|
|
603
|
-
datasetId = status.dataSetId?.toString() || void 0;
|
|
604
|
-
console.log(`[Dataset] Ready (ID: ${status.dataSetId?.toString()})`);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
752
|
return {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
txHash,
|
|
613
|
-
message: "Dataset created successfully"
|
|
753
|
+
...result,
|
|
754
|
+
progressLog
|
|
614
755
|
};
|
|
615
|
-
} catch (error) {
|
|
616
|
-
return createErrorResponse(
|
|
617
|
-
"dataset_creation_failed",
|
|
618
|
-
`Failed to create dataset: ${error.message}`,
|
|
619
|
-
{ success: false }
|
|
620
|
-
);
|
|
621
756
|
}
|
|
757
|
+
return {
|
|
758
|
+
...result,
|
|
759
|
+
progressLog
|
|
760
|
+
};
|
|
622
761
|
}
|
|
623
762
|
});
|
|
624
763
|
var datasetTools = {
|
|
625
764
|
getDataset,
|
|
626
|
-
getDatasets
|
|
765
|
+
getDatasets,
|
|
627
766
|
createDataset
|
|
628
767
|
};
|
|
629
768
|
|
|
@@ -653,7 +792,6 @@ var uploadFile = createTool2({
|
|
|
653
792
|
const synapse = await getSynapseInstance();
|
|
654
793
|
log("Checking storage balance...");
|
|
655
794
|
const storageMetrics = await checkStorageBalance(
|
|
656
|
-
synapse,
|
|
657
795
|
fileInfo.size,
|
|
658
796
|
env.PERSISTENCE_PERIOD_DAYS
|
|
659
797
|
);
|
|
@@ -662,22 +800,25 @@ var uploadFile = createTool2({
|
|
|
662
800
|
return {
|
|
663
801
|
success: false,
|
|
664
802
|
error: "insufficient_balance",
|
|
665
|
-
message:
|
|
666
|
-
required: {
|
|
667
|
-
deposit: fromBaseUnits(storageMetrics.depositNeeded, 18)
|
|
668
|
-
}
|
|
803
|
+
message: `Insufficient balance: ${fromBaseUnits(storageMetrics.depositNeeded, 18)} USDFC required. Enable autoPayment parameter or use processPayment tool to deposit funds first.`
|
|
669
804
|
};
|
|
670
805
|
}
|
|
671
806
|
log(storageMetrics.depositNeeded > 0n ? "Insufficient balance, processing payment..." : "Insufficient service approvals, approving service...");
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
storageMetrics.depositNeeded
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
807
|
+
log(`Deposit needed: ${fromBaseUnits(storageMetrics.depositNeeded, 18)} USDFC`);
|
|
808
|
+
if (storageMetrics.depositNeeded > 0n || !storageMetrics.isSufficient) {
|
|
809
|
+
const { success } = await processPaymentService(BigInt(storageMetrics.depositNeeded));
|
|
810
|
+
if (!success) {
|
|
811
|
+
return createErrorResponse(
|
|
812
|
+
"payment_failed",
|
|
813
|
+
"Failed to process payment",
|
|
814
|
+
{ success: false }
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
log("Payment processed successfully");
|
|
818
|
+
}
|
|
678
819
|
}
|
|
679
820
|
log("Creating storage service...");
|
|
680
|
-
const storageService = await synapse.
|
|
821
|
+
const storageService = await synapse.storage.createContext({
|
|
681
822
|
dataSetId: context.datasetId ? Number(context.datasetId) : void 0,
|
|
682
823
|
withCDN: context.withCDN || false,
|
|
683
824
|
callbacks: {
|
|
@@ -686,16 +827,8 @@ var uploadFile = createTool2({
|
|
|
686
827
|
log(`Dataset provider: ${Number(info.provider.id)}`);
|
|
687
828
|
log(`Is existing dataset: ${info.isExisting}`);
|
|
688
829
|
},
|
|
689
|
-
|
|
690
|
-
log(`
|
|
691
|
-
},
|
|
692
|
-
onDataSetCreationProgress: (status) => {
|
|
693
|
-
if (status.serverConfirmed) {
|
|
694
|
-
log(`Dataset ready (ID: ${status.dataSetId})`);
|
|
695
|
-
}
|
|
696
|
-
},
|
|
697
|
-
onProviderSelected: (provider) => {
|
|
698
|
-
log(`Provider selected: ${provider.id}`);
|
|
830
|
+
onProviderSelected: (provider2) => {
|
|
831
|
+
log(`Provider selected: ${provider2.id}`);
|
|
699
832
|
}
|
|
700
833
|
}
|
|
701
834
|
});
|
|
@@ -706,19 +839,21 @@ var uploadFile = createTool2({
|
|
|
706
839
|
onUploadComplete: (piece) => {
|
|
707
840
|
log(`Upload complete (pieceCid: ${piece.toV1().toString()})`);
|
|
708
841
|
},
|
|
709
|
-
onPieceAdded: (
|
|
710
|
-
uploadTxHash =
|
|
711
|
-
log(`Piece added to dataset (tx: ${
|
|
842
|
+
onPieceAdded: (hash) => {
|
|
843
|
+
uploadTxHash = hash;
|
|
844
|
+
log(`Piece added to dataset (tx: ${hash || "pending"})`);
|
|
712
845
|
},
|
|
713
846
|
onPieceConfirmed: () => {
|
|
714
847
|
log("Piece confirmed on blockchain");
|
|
715
848
|
}
|
|
716
849
|
});
|
|
850
|
+
const provider = await getProvider(storageService.provider.id);
|
|
717
851
|
const getRetrievalUrl = async (pieceCid2) => {
|
|
718
852
|
if (context.withCDN) {
|
|
719
|
-
|
|
853
|
+
const network = env.FILECOIN_NETWORK === "mainnet" ? "mainnet" : "calibration";
|
|
854
|
+
return `https://${await synapse.getSigner().getAddress()}.${network}.filbeam.io/${pieceCid2}`;
|
|
720
855
|
} else {
|
|
721
|
-
const serviceURL =
|
|
856
|
+
const serviceURL = provider.pdp.serviceURL;
|
|
722
857
|
const endsWithSlash = serviceURL.endsWith("/");
|
|
723
858
|
const serviceURLWithoutSlash = endsWithSlash ? serviceURL.slice(0, -1) : serviceURL;
|
|
724
859
|
return `${serviceURLWithoutSlash}/piece/${pieceCid2}`;
|
|
@@ -730,6 +865,9 @@ var uploadFile = createTool2({
|
|
|
730
865
|
console.log(`TX Hash: ${uploadTxHash}`);
|
|
731
866
|
console.log(`File Name: ${fileName}`);
|
|
732
867
|
console.log(`File Size: ${fileInfo.size}`);
|
|
868
|
+
if (uploadTxHash) {
|
|
869
|
+
log(`View transaction: ${getExplorerUrl(uploadTxHash)}`);
|
|
870
|
+
}
|
|
733
871
|
return {
|
|
734
872
|
success: true,
|
|
735
873
|
pieceCid: pieceCid.toV1().toString(),
|
|
@@ -760,24 +898,53 @@ var fileTools = {
|
|
|
760
898
|
import { createTool as createTool3 } from "@mastra/core";
|
|
761
899
|
var getBalances = createTool3({
|
|
762
900
|
id: "getBalances",
|
|
763
|
-
description: "Check wallet balances (FIL and USDFC tokens) and comprehensive storage metrics including available funds, required deposits, days of storage remaining, and allowance status. Returns both human-readable formatted values and raw data. Use this before upload operations to verify sufficient balance, or to monitor storage budget and plan deposits.
|
|
901
|
+
description: "Check wallet balances (FIL and USDFC tokens) and comprehensive storage metrics including available funds, required deposits, days of storage remaining, and allowance status. Returns both human-readable formatted values and raw data with progress log showing calculation parameters used. \u26A0\uFE0F CRITICAL: Storage providers consider accounts with less than 30 days of available balance as INSOLVENT and may refuse service. Default notice period is 45 days to ensure safe margin. IMPORTANT AGENT INSTRUCTIONS: (1) Before calling this tool, ASK the user if they want to calculate based on default storage requirements (150 GiB capacity, 365 days persistence, 45 days notice period) or if they have specific requirements. (2) After showing results, ALWAYS ASK the user if they want to see calculations for different storage configurations. (3) If days remaining falls below 45, WARN the user that they are approaching insolvency threshold (30 days) and should deposit funds immediately. Use this before upload operations to verify sufficient balance, or to monitor storage budget and plan deposits.",
|
|
764
902
|
inputSchema: GetBalancesSchema,
|
|
765
903
|
outputSchema: GetBalancesOutputSchema,
|
|
766
904
|
execute: async ({ context }) => {
|
|
905
|
+
const progressLog = [];
|
|
906
|
+
const log = (msg) => {
|
|
907
|
+
progressLog.push(msg);
|
|
908
|
+
};
|
|
767
909
|
try {
|
|
768
|
-
const
|
|
769
|
-
const
|
|
910
|
+
const storageCapacityBytes = context.storageCapacityBytes || env.TOTAL_STORAGE_NEEDED_GiB * 1024 * 1024 * 1024;
|
|
911
|
+
const persistencePeriodDays = context.persistencePeriodDays || env.PERSISTENCE_PERIOD_DAYS;
|
|
912
|
+
const notificationThresholdDays = context.notificationThresholdDays || env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS;
|
|
913
|
+
const capacityGB = (storageCapacityBytes / (1024 * 1024 * 1024)).toFixed(2);
|
|
914
|
+
const capacityTB = (storageCapacityBytes / (1024 * 1024 * 1024 * 1024)).toFixed(2);
|
|
915
|
+
log(`Calculating balance requirements with:`);
|
|
916
|
+
log(`- Storage Capacity: ${capacityGB} GB (${capacityTB} TB)`);
|
|
917
|
+
log(`- Persistence Period: ${persistencePeriodDays} days`);
|
|
918
|
+
log(`- Notification Threshold: ${notificationThresholdDays} days`);
|
|
919
|
+
log("Fetching wallet balances from blockchain...");
|
|
920
|
+
const checkStorageBalanceResult = await checkStorageBalance(storageCapacityBytes, persistencePeriodDays);
|
|
921
|
+
log("Calculating storage metrics and requirements...");
|
|
922
|
+
const formattedResult = formatStorageBalanceResult(checkStorageBalanceResult);
|
|
923
|
+
const serializedResult = serializeBigInt(checkStorageBalanceResult);
|
|
924
|
+
log(`Balance check complete. Available USDFC: ${formattedResult.usdfcBalance}`);
|
|
925
|
+
const daysLeft = checkStorageBalanceResult.daysLeftAtBurnRate;
|
|
926
|
+
if (daysLeft < 45) {
|
|
927
|
+
if (daysLeft < 30) {
|
|
928
|
+
log(`\u26D4 CRITICAL INSOLVENCY WARNING: Only ${daysLeft.toFixed(1)} days of balance remaining! Storage providers consider accounts with less than 30 days as INSOLVENT and will REFUSE SERVICE. Deposit funds IMMEDIATELY!`);
|
|
929
|
+
} else {
|
|
930
|
+
log(`\u26A0\uFE0F LOW BALANCE WARNING: Only ${daysLeft.toFixed(1)} days remaining (threshold: 45 days). You are approaching the insolvency threshold (30 days). Please deposit funds soon to avoid service interruption.`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
log("\u{1F4A1} To calculate for different storage requirements, call this tool again with custom storageCapacityBytes and persistencePeriodDays parameters.");
|
|
770
934
|
return {
|
|
771
935
|
success: true,
|
|
772
|
-
checkStorageBalanceResultFormatted:
|
|
773
|
-
checkStorageBalanceResult:
|
|
936
|
+
checkStorageBalanceResultFormatted: formattedResult,
|
|
937
|
+
checkStorageBalanceResult: serializedResult,
|
|
938
|
+
progressLog,
|
|
939
|
+
message: `Balance check complete for ${capacityTB} TB over ${persistencePeriodDays} days. Available: ${formattedResult.usdfcBalance}`
|
|
774
940
|
};
|
|
775
941
|
} catch (error) {
|
|
776
|
-
return
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
942
|
+
return {
|
|
943
|
+
success: false,
|
|
944
|
+
error: "balance_fetch_failed",
|
|
945
|
+
message: `Failed to fetch balances: ${error.message}`,
|
|
946
|
+
progressLog
|
|
947
|
+
};
|
|
781
948
|
}
|
|
782
949
|
}
|
|
783
950
|
});
|
|
@@ -789,61 +956,82 @@ var balanceTools = {
|
|
|
789
956
|
import { createTool as createTool4 } from "@mastra/core";
|
|
790
957
|
var processPayment = createTool4({
|
|
791
958
|
id: "processPayment",
|
|
792
|
-
description: "Deposit USDFC tokens and configure storage service allowances in a single transaction using EIP-2612 gasless permits. Sets both rate allowance (per-epoch spending limit) and lockup allowance (total committed funds) to unlimited for seamless storage operations. Use this to fund your storage account before uploads or when balance is insufficient. Validates wallet balance before processing to prevent failed transactions.",
|
|
959
|
+
description: "Deposit USDFC tokens (not in base units) and configure storage service allowances in a single transaction using EIP-2612 gasless permits. Sets both rate allowance (per-epoch spending limit) and lockup allowance (total committed funds) to unlimited for seamless storage operations. Use this to fund your storage account before uploads or when balance is insufficient. Validates wallet balance before processing to prevent failed transactions.",
|
|
793
960
|
inputSchema: ProcessPaymentSchema,
|
|
794
961
|
outputSchema: ProcessPaymentOutputSchema,
|
|
795
962
|
execute: async ({ context }) => {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
required: {
|
|
807
|
-
deposit: depositAmount
|
|
808
|
-
},
|
|
809
|
-
available: availableFunds
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
if (availableFunds < depositAmount) {
|
|
813
|
-
return {
|
|
814
|
-
success: false,
|
|
815
|
-
error: "insufficient_balance",
|
|
816
|
-
message: `Insufficient USDFC balance. Required: ${depositAmount}, Available: ${availableFunds}`,
|
|
817
|
-
required: depositAmount,
|
|
818
|
-
available: Number(availableFunds)
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
const result = await processStoragePayment(
|
|
822
|
-
synapse,
|
|
823
|
-
BigInt(depositAmount),
|
|
824
|
-
env.PERSISTENCE_PERIOD_DAYS
|
|
825
|
-
);
|
|
963
|
+
const progressLog = [];
|
|
964
|
+
const log = (msg) => {
|
|
965
|
+
progressLog.push(msg);
|
|
966
|
+
};
|
|
967
|
+
const { depositAmount } = context;
|
|
968
|
+
log("Converting amount to base units...");
|
|
969
|
+
const amount = toBaseUnits(depositAmount.toString(), 18);
|
|
970
|
+
log("Initiating payment transaction...");
|
|
971
|
+
const { success, txHash, error } = await processPaymentService(amount);
|
|
972
|
+
if (!success) {
|
|
826
973
|
return {
|
|
827
|
-
success:
|
|
828
|
-
|
|
829
|
-
message: `
|
|
974
|
+
success: false,
|
|
975
|
+
error: "payment_failed",
|
|
976
|
+
message: `Failed to process payment: ${error}`,
|
|
977
|
+
progressLog
|
|
830
978
|
};
|
|
831
|
-
} catch (error) {
|
|
832
|
-
return createErrorResponse(
|
|
833
|
-
"payment_failed",
|
|
834
|
-
`Payment processing failed: ${error.message}`,
|
|
835
|
-
{ success: false }
|
|
836
|
-
);
|
|
837
979
|
}
|
|
980
|
+
log("Payment transaction confirmed");
|
|
981
|
+
if (txHash) {
|
|
982
|
+
log(`View transaction: ${getExplorerUrl(txHash)}`);
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
success,
|
|
986
|
+
txHash,
|
|
987
|
+
message: `Payment processed successfully. You deposited ${depositAmount} USDFC to your storage account.`,
|
|
988
|
+
progressLog
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
var processWithdrawal = createTool4({
|
|
993
|
+
id: "processWithdrawal",
|
|
994
|
+
description: "Withdraw USDFC tokens (not in base units) from the storage account back to your wallet. Reduces storage service allowances and available balance. Use this to retrieve unused funds from the storage account. Returns transaction hash for verification and progress tracking through conversion, initiation, and confirmation steps.",
|
|
995
|
+
inputSchema: ProcessWithdrawalSchema,
|
|
996
|
+
outputSchema: ProcessWithdrawalOutputSchema,
|
|
997
|
+
execute: async ({ context }) => {
|
|
998
|
+
const progressLog = [];
|
|
999
|
+
const log = (msg) => {
|
|
1000
|
+
progressLog.push(msg);
|
|
1001
|
+
};
|
|
1002
|
+
const { withdrawalAmount } = context;
|
|
1003
|
+
log("Converting amount to base units...");
|
|
1004
|
+
const amount = toBaseUnits(withdrawalAmount.toString(), 18);
|
|
1005
|
+
log("Initiating withdrawal transaction...");
|
|
1006
|
+
const { success, txHash, error } = await processWithdrawalService(amount);
|
|
1007
|
+
if (!success) {
|
|
1008
|
+
return {
|
|
1009
|
+
success: false,
|
|
1010
|
+
error: "withdrawal_failed",
|
|
1011
|
+
message: `Failed to process withdrawal: ${error}`,
|
|
1012
|
+
progressLog
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
log("Withdrawal transaction confirmed");
|
|
1016
|
+
if (txHash) {
|
|
1017
|
+
log(`View transaction: ${getExplorerUrl(txHash)}`);
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
success: true,
|
|
1021
|
+
txHash,
|
|
1022
|
+
message: `Withdrawal processed successfully. You withdrew ${withdrawalAmount} USDFC from your storage account.`,
|
|
1023
|
+
progressLog
|
|
1024
|
+
};
|
|
838
1025
|
}
|
|
839
1026
|
});
|
|
840
1027
|
var paymentTools = {
|
|
841
|
-
processPayment
|
|
1028
|
+
processPayment,
|
|
1029
|
+
processWithdrawal
|
|
842
1030
|
};
|
|
843
1031
|
|
|
844
1032
|
// src/mastra/tools/provider-tools.ts
|
|
845
1033
|
import { createTool as createTool5 } from "@mastra/core";
|
|
846
|
-
import {
|
|
1034
|
+
import { readProviders as readProviders2 } from "@filoz/synapse-core/warm-storage";
|
|
847
1035
|
var getProviders = createTool5({
|
|
848
1036
|
id: "getProviders",
|
|
849
1037
|
description: "List storage providers available on the Filecoin network with their service details, product offerings, and endpoint URLs. By default returns only approved providers for reliability. Use this to discover available providers, select specific providers for dataset creation, or verify provider availability before operations. Provider information includes service URLs needed for file retrieval.",
|
|
@@ -851,21 +1039,11 @@ var getProviders = createTool5({
|
|
|
851
1039
|
outputSchema: GetProvidersOutputSchema,
|
|
852
1040
|
execute: async ({ context }) => {
|
|
853
1041
|
try {
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1042
|
+
const providers = (await readProviders2(
|
|
1043
|
+
publicClient
|
|
1044
|
+
)).filter(
|
|
1045
|
+
(p) => context.onlyApproved ? p.isActive : true
|
|
858
1046
|
);
|
|
859
|
-
const approvedProviderIds = await warmStorageService.getApprovedProviderIds();
|
|
860
|
-
const providersInfo = await Promise.all(
|
|
861
|
-
approvedProviderIds.map(async (providerId) => {
|
|
862
|
-
const providerInfo = await synapse.getProviderInfo(providerId);
|
|
863
|
-
return providerInfo;
|
|
864
|
-
})
|
|
865
|
-
);
|
|
866
|
-
const providers = context.onlyApproved !== false ? providersInfo.filter(
|
|
867
|
-
(p) => approvedProviderIds.includes(p.id)
|
|
868
|
-
) : providersInfo;
|
|
869
1047
|
return {
|
|
870
1048
|
success: true,
|
|
871
1049
|
providers: providers.map(serializeBigInt),
|
|
@@ -873,10 +1051,12 @@ var getProviders = createTool5({
|
|
|
873
1051
|
message: `Found ${providers.length} provider(s)`
|
|
874
1052
|
};
|
|
875
1053
|
} catch (error) {
|
|
876
|
-
return
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1054
|
+
return {
|
|
1055
|
+
success: false,
|
|
1056
|
+
providers: [],
|
|
1057
|
+
error: error.message,
|
|
1058
|
+
message: `Failed to fetch providers: ${error.message}`
|
|
1059
|
+
};
|
|
880
1060
|
}
|
|
881
1061
|
}
|
|
882
1062
|
});
|
|
@@ -884,35 +1064,496 @@ var providerTools = {
|
|
|
884
1064
|
getProviders
|
|
885
1065
|
};
|
|
886
1066
|
|
|
1067
|
+
// src/mastra/tools/storage-cost-tools.ts
|
|
1068
|
+
import { createTool as createTool6 } from "@mastra/core";
|
|
1069
|
+
import { z as z3 } from "zod";
|
|
1070
|
+
import { SIZE_CONSTANTS as SIZE_CONSTANTS4 } from "@filoz/synapse-core/utils";
|
|
1071
|
+
var EstimateStorageCostSchema = z3.object({
|
|
1072
|
+
sizeInGiB: z3.number().positive().optional().describe("Size of data to store in GiB (gibibytes). Example: 1.5 for 1.5 GiB. Provide either sizeInGiB or sizeInTiB, not both"),
|
|
1073
|
+
sizeInTiB: z3.number().positive().optional().describe("Size of data to store in TiB (tebibytes). Example: 0.5 for 0.5 TiB. Provide either sizeInGiB or sizeInTiB, not both"),
|
|
1074
|
+
durationInMonths: z3.number().positive().int().describe("Duration to store data in months. Example: 12 for one year"),
|
|
1075
|
+
createCDNDataset: z3.boolean().optional().default(false).describe("Whether this is for a new CDN-enabled dataset (adds 1 USDFC one-time cost). Default: false")
|
|
1076
|
+
}).refine(
|
|
1077
|
+
(data) => data.sizeInGiB !== void 0 !== (data.sizeInTiB !== void 0),
|
|
1078
|
+
{
|
|
1079
|
+
message: "Exactly one of sizeInGiB or sizeInTiB must be provided"
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
var EstimateStorageCostOutputSchema = z3.object({
|
|
1083
|
+
success: z3.boolean(),
|
|
1084
|
+
// Success fields
|
|
1085
|
+
monthlyCost: z3.string().optional().describe("Monthly storage cost in USDFC"),
|
|
1086
|
+
totalCost: z3.string().optional().describe("Total storage cost for duration in USDFC (includes CDN setup if applicable)"),
|
|
1087
|
+
cdnSetupCost: z3.string().optional().describe("One-time CDN setup cost in USDFC (if createCDNDataset is true)"),
|
|
1088
|
+
cdnEgressCredits: z3.string().optional().describe("CDN egress credits topped up (in GiB) with the 1 USDFC setup cost"),
|
|
1089
|
+
details: z3.object({
|
|
1090
|
+
sizeInBytes: z3.number(),
|
|
1091
|
+
sizeFormatted: z3.string().describe('Human-readable size (e.g., "1.50 GiB")'),
|
|
1092
|
+
durationInMonths: z3.number(),
|
|
1093
|
+
pricePerTiBPerMonth: z3.string(),
|
|
1094
|
+
minimumPricePerMonth: z3.string(),
|
|
1095
|
+
appliedMinimum: z3.boolean().describe("Whether minimum pricing was applied (for storage < 24.567 GiB)")
|
|
1096
|
+
}).optional(),
|
|
1097
|
+
breakdown: z3.string().optional().describe("Human-readable cost breakdown"),
|
|
1098
|
+
pricingExplanation: z3.string().optional().describe("Detailed explanation of how storage pricing works"),
|
|
1099
|
+
cdnPricingExplanation: z3.string().optional().describe("Detailed explanation of how CDN egress pricing works"),
|
|
1100
|
+
// Error fields
|
|
1101
|
+
error: z3.string().optional(),
|
|
1102
|
+
message: z3.string()
|
|
1103
|
+
});
|
|
1104
|
+
var estimateStoragePricing = createTool6({
|
|
1105
|
+
id: "estimateStoragePricing",
|
|
1106
|
+
description: "Calculate storage costs for Filecoin OnchainCloud and explain pricing models. Provides: (1) Cost estimates with monthly/total breakdowns, (2) Comprehensive explanation of storage pricing (pay-per-epoch, $2.50/TiB/month, $0.06 minimum), (3) CDN egress pricing details ($7/TiB downloads, 1 USDFC = ~146 GiB credits). \u26A0\uFE0F CRITICAL: When explaining budgeting, ALWAYS warn that storage providers consider accounts with less than 30 days of remaining balance as INSOLVENT and may refuse service. Recommend maintaining at least 45 days of balance for safety margin. Use when users ask about storage costs, pricing models, CDN fees, or need to budget for storage. Clarifies that CDN 1 USDFC is NOT a fee but pre-paid egress credits that can be topped up anytime.",
|
|
1107
|
+
inputSchema: EstimateStorageCostSchema,
|
|
1108
|
+
outputSchema: EstimateStorageCostOutputSchema,
|
|
1109
|
+
execute: async ({ context }) => {
|
|
1110
|
+
try {
|
|
1111
|
+
const { sizeInGiB, sizeInTiB, durationInMonths, createCDNDataset } = context;
|
|
1112
|
+
let sizeInBytes;
|
|
1113
|
+
if (sizeInGiB !== void 0) {
|
|
1114
|
+
sizeInBytes = sizeInGiB * Number(SIZE_CONSTANTS4.GiB);
|
|
1115
|
+
} else if (sizeInTiB !== void 0) {
|
|
1116
|
+
sizeInBytes = sizeInTiB * Number(SIZE_CONSTANTS4.TiB);
|
|
1117
|
+
} else {
|
|
1118
|
+
throw new Error("Either sizeInGiB or sizeInTiB must be provided");
|
|
1119
|
+
}
|
|
1120
|
+
const estimate = await estimateStorageCost(
|
|
1121
|
+
sizeInBytes,
|
|
1122
|
+
durationInMonths,
|
|
1123
|
+
createCDNDataset
|
|
1124
|
+
);
|
|
1125
|
+
const cdnEgressCreditsGiB = createCDNDataset ? 1 / CDN_EGRESS_RATE_PER_TIB * 1024 : 0;
|
|
1126
|
+
const breakdown = [
|
|
1127
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1128
|
+
`Storage Cost Estimate for ${estimate.details.sizeFormatted}`,
|
|
1129
|
+
`Duration: ${durationInMonths} month${durationInMonths !== 1 ? "s" : ""}`,
|
|
1130
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1131
|
+
``,
|
|
1132
|
+
`Monthly Storage Cost: ${Number(estimate.monthlyCost) / 1e18} USDFC`,
|
|
1133
|
+
`Total Storage Cost: ${Number(estimate.totalCost - estimate.cdnSetupCost) / 1e18} USDFC`
|
|
1134
|
+
];
|
|
1135
|
+
if (createCDNDataset && estimate.cdnSetupCost > 0n) {
|
|
1136
|
+
breakdown.push(``);
|
|
1137
|
+
breakdown.push(`CDN Egress Credits Top-up: ${estimate.cdnSetupCostFormatted} USDFC`);
|
|
1138
|
+
breakdown.push(` \u2192 Provides ~${cdnEgressCreditsGiB.toFixed(2)} GiB of egress credits`);
|
|
1139
|
+
breakdown.push(` \u2192 You can top up more credits anytime`);
|
|
1140
|
+
breakdown.push(``);
|
|
1141
|
+
breakdown.push(`Grand Total (Storage + CDN Credits): ${estimate.totalCostFormatted} USDFC`);
|
|
1142
|
+
}
|
|
1143
|
+
breakdown.push(``);
|
|
1144
|
+
breakdown.push(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
1145
|
+
if (estimate.details.appliedMinimum) {
|
|
1146
|
+
breakdown.push(
|
|
1147
|
+
`Note: Minimum pricing of $0.06/month applied (storage < ~24.567 GiB)`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
const pricingExplanation = [
|
|
1151
|
+
``,
|
|
1152
|
+
`HOW STORAGE PRICING WORKS:`,
|
|
1153
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1154
|
+
``,
|
|
1155
|
+
`Storage operates on a pay-per-epoch model (epochs are 30 seconds):`,
|
|
1156
|
+
``,
|
|
1157
|
+
`1. BASE RATE: $2.50 per TiB per month`,
|
|
1158
|
+
` - Calculated per epoch: (size / TiB) \xD7 $2.50/month \xF7 epochs_per_month`,
|
|
1159
|
+
` - You pay only for the storage space you use`,
|
|
1160
|
+
``,
|
|
1161
|
+
`2. MINIMUM CHARGE: $0.06 per month`,
|
|
1162
|
+
` - Applies to storage < ~24.567 GiB`,
|
|
1163
|
+
` - Ensures service sustainability for small datasets`,
|
|
1164
|
+
``,
|
|
1165
|
+
`3. PAYMENT MODEL:`,
|
|
1166
|
+
` - Deposit USDFC tokens into your account`,
|
|
1167
|
+
` - Set rate allowance (max spending per epoch)`,
|
|
1168
|
+
` - Set lockup allowance (max total locked funds)`,
|
|
1169
|
+
` - Storage service deducts costs automatically each epoch`,
|
|
1170
|
+
``,
|
|
1171
|
+
`4. EPOCHS:`,
|
|
1172
|
+
` - 1 epoch = 30 seconds`,
|
|
1173
|
+
` - ~2,880 epochs per day`,
|
|
1174
|
+
` - ~86,400 epochs per month (30 days)`,
|
|
1175
|
+
``,
|
|
1176
|
+
`\u26A0\uFE0F CRITICAL - INSOLVENCY WARNING:`,
|
|
1177
|
+
` - Storage providers consider accounts with LESS THAN 30 DAYS of`,
|
|
1178
|
+
` remaining balance as INSOLVENT`,
|
|
1179
|
+
` - Insolvent accounts may be REFUSED SERVICE or have data removed`,
|
|
1180
|
+
` - ALWAYS maintain at least 45 days of balance for safety margin`,
|
|
1181
|
+
` - Default notification threshold: 45 days (gives time to deposit)`,
|
|
1182
|
+
` - Monitor your balance regularly and top up before hitting 30 days`,
|
|
1183
|
+
``
|
|
1184
|
+
].join("\n");
|
|
1185
|
+
const cdnPricingExplanation = [
|
|
1186
|
+
``,
|
|
1187
|
+
`HOW CDN EGRESS PRICING WORKS:`,
|
|
1188
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1189
|
+
``,
|
|
1190
|
+
`CDN enables fast file retrieval with egress (download) charges:`,
|
|
1191
|
+
``,
|
|
1192
|
+
`1. EGRESS RATE: $7 per TiB downloaded`,
|
|
1193
|
+
` - ~$0.0068 per GiB downloaded`,
|
|
1194
|
+
` - Only pay for actual data transferred`,
|
|
1195
|
+
``,
|
|
1196
|
+
`2. CDN CREDITS TOP-UP (Not a Fee!):`,
|
|
1197
|
+
` - Creating a new CDN dataset requires 1 USDFC top-up`,
|
|
1198
|
+
` - This is NOT a fee - it's pre-paid egress credits`,
|
|
1199
|
+
` - 1 USDFC = ~146.29 GiB of download credits`,
|
|
1200
|
+
` - Credits are deducted as files are downloaded`,
|
|
1201
|
+
``,
|
|
1202
|
+
`3. REUSING DATASETS:`,
|
|
1203
|
+
` - Adding files to existing CDN dataset = NO additional top-up`,
|
|
1204
|
+
` - Only the first dataset creation requires the 1 USDFC top-up`,
|
|
1205
|
+
` - Your egress credits work across all your CDN datasets`,
|
|
1206
|
+
``,
|
|
1207
|
+
`4. TOPPING UP CREDITS:`,
|
|
1208
|
+
` - Top up anytime to avoid running out`,
|
|
1209
|
+
` - Monitor your remaining egress credits`,
|
|
1210
|
+
` - No expiration on credits`,
|
|
1211
|
+
``,
|
|
1212
|
+
`5. EXAMPLE CALCULATIONS:`,
|
|
1213
|
+
` - 1 USDFC \u2192 ~146 GiB of downloads`,
|
|
1214
|
+
` - 10 USDFC \u2192 ~1.43 TiB of downloads`,
|
|
1215
|
+
` - 70 USDFC \u2192 ~10 TiB of downloads`,
|
|
1216
|
+
``,
|
|
1217
|
+
`6. WHEN TO USE CDN:`,
|
|
1218
|
+
` - Frequently accessed files`,
|
|
1219
|
+
` - Public-facing content`,
|
|
1220
|
+
` - Applications requiring fast retrieval`,
|
|
1221
|
+
` - Skip CDN for archival/backup storage`,
|
|
1222
|
+
``
|
|
1223
|
+
].join("\n");
|
|
1224
|
+
return {
|
|
1225
|
+
success: true,
|
|
1226
|
+
monthlyCost: (Number(estimate.monthlyCost) / 1e18).toString(),
|
|
1227
|
+
totalCost: estimate.totalCostFormatted,
|
|
1228
|
+
cdnSetupCost: estimate.cdnSetupCostFormatted,
|
|
1229
|
+
cdnEgressCredits: createCDNDataset ? `${cdnEgressCreditsGiB.toFixed(2)} GiB (~${(cdnEgressCreditsGiB / 1024).toFixed(4)} TiB)` : "0 GiB",
|
|
1230
|
+
details: {
|
|
1231
|
+
...estimate.details,
|
|
1232
|
+
pricePerTiBPerMonth: estimate.details.pricePerTiBPerMonth.toString(),
|
|
1233
|
+
minimumPricePerMonth: estimate.details.minimumPricePerMonth.toString()
|
|
1234
|
+
},
|
|
1235
|
+
breakdown: breakdown.join("\n"),
|
|
1236
|
+
pricingExplanation,
|
|
1237
|
+
cdnPricingExplanation,
|
|
1238
|
+
message: `Estimated cost: ${estimate.totalCostFormatted} USDFC for ${estimate.details.sizeFormatted} over ${durationInMonths} month${durationInMonths !== 1 ? "s" : ""}${createCDNDataset ? " (includes 1 USDFC CDN egress credits top-up = ~" + cdnEgressCreditsGiB.toFixed(2) + " GiB of downloads)" : ""}`
|
|
1239
|
+
};
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
return {
|
|
1242
|
+
success: false,
|
|
1243
|
+
error: error.message,
|
|
1244
|
+
message: `Failed to estimate storage cost: ${error.message}`
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
var convertStorageSize = createTool6({
|
|
1250
|
+
id: "convertStorageSize",
|
|
1251
|
+
description: "Convert storage sizes between different units (bytes, KiB, MiB, GiB, TiB). Useful when users provide sizes in different formats and you need to calculate storage costs.",
|
|
1252
|
+
inputSchema: z3.object({
|
|
1253
|
+
value: z3.number().positive().describe("The numeric value to convert"),
|
|
1254
|
+
fromUnit: z3.enum(["bytes", "KiB", "MiB", "GiB", "TiB"]).describe("The unit to convert from"),
|
|
1255
|
+
toUnit: z3.enum(["bytes", "KiB", "MiB", "GiB", "TiB"]).optional().describe("The unit to convert to. If not specified, returns all units")
|
|
1256
|
+
}),
|
|
1257
|
+
outputSchema: z3.object({
|
|
1258
|
+
success: z3.boolean(),
|
|
1259
|
+
bytes: z3.number().optional(),
|
|
1260
|
+
KiB: z3.number().optional(),
|
|
1261
|
+
MiB: z3.number().optional(),
|
|
1262
|
+
GiB: z3.number().optional(),
|
|
1263
|
+
TiB: z3.number().optional(),
|
|
1264
|
+
message: z3.string(),
|
|
1265
|
+
error: z3.string().optional()
|
|
1266
|
+
}),
|
|
1267
|
+
execute: async ({ context }) => {
|
|
1268
|
+
try {
|
|
1269
|
+
const { value, fromUnit, toUnit } = context;
|
|
1270
|
+
let bytes;
|
|
1271
|
+
switch (fromUnit) {
|
|
1272
|
+
case "bytes":
|
|
1273
|
+
bytes = value;
|
|
1274
|
+
break;
|
|
1275
|
+
case "KiB":
|
|
1276
|
+
bytes = value * Number(SIZE_CONSTANTS4.KiB);
|
|
1277
|
+
break;
|
|
1278
|
+
case "MiB":
|
|
1279
|
+
bytes = value * Number(SIZE_CONSTANTS4.MiB);
|
|
1280
|
+
break;
|
|
1281
|
+
case "GiB":
|
|
1282
|
+
bytes = value * Number(SIZE_CONSTANTS4.GiB);
|
|
1283
|
+
break;
|
|
1284
|
+
case "TiB":
|
|
1285
|
+
bytes = value * Number(SIZE_CONSTANTS4.TiB);
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
if (toUnit) {
|
|
1289
|
+
let result;
|
|
1290
|
+
switch (toUnit) {
|
|
1291
|
+
case "bytes":
|
|
1292
|
+
result = bytes;
|
|
1293
|
+
break;
|
|
1294
|
+
case "KiB":
|
|
1295
|
+
result = bytes / Number(SIZE_CONSTANTS4.KiB);
|
|
1296
|
+
break;
|
|
1297
|
+
case "MiB":
|
|
1298
|
+
result = bytes / Number(SIZE_CONSTANTS4.MiB);
|
|
1299
|
+
break;
|
|
1300
|
+
case "GiB":
|
|
1301
|
+
result = bytes / Number(SIZE_CONSTANTS4.GiB);
|
|
1302
|
+
break;
|
|
1303
|
+
case "TiB":
|
|
1304
|
+
result = bytes / Number(SIZE_CONSTANTS4.TiB);
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
return {
|
|
1308
|
+
success: true,
|
|
1309
|
+
[toUnit]: result,
|
|
1310
|
+
message: `${value} ${fromUnit} = ${result.toFixed(4)} ${toUnit}`
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
success: true,
|
|
1315
|
+
bytes,
|
|
1316
|
+
KiB: bytes / Number(SIZE_CONSTANTS4.KiB),
|
|
1317
|
+
MiB: bytes / Number(SIZE_CONSTANTS4.MiB),
|
|
1318
|
+
GiB: bytes / Number(SIZE_CONSTANTS4.GiB),
|
|
1319
|
+
TiB: bytes / Number(SIZE_CONSTANTS4.TiB),
|
|
1320
|
+
message: `Converted ${value} ${fromUnit} to all units`
|
|
1321
|
+
};
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
return {
|
|
1324
|
+
success: false,
|
|
1325
|
+
error: error.message,
|
|
1326
|
+
message: `Failed to convert storage size: ${error.message}`
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
var getStoragePricingInfo = createTool6({
|
|
1332
|
+
id: "getStoragePricingInfo",
|
|
1333
|
+
description: "Get comprehensive information about Filecoin OnchainCloud storage pricing, including cost structure, CDN egress pricing, payment model, and a detailed example of storing 1 TiB for 1 year. \u26A0\uFE0F CRITICAL: ALWAYS include the insolvency warning (accounts with less than 30 days balance are considered insolvent and may be refused service). Recommend 45-day minimum balance. Use this when users ask general questions about 'how much does storage cost', 'explain storage pricing', 'how does billing work', or want to understand the pricing model before calculating specific costs.",
|
|
1334
|
+
inputSchema: z3.object({
|
|
1335
|
+
includeCDNExample: z3.boolean().optional().default(true).describe("Include CDN pricing example. Default: true")
|
|
1336
|
+
}),
|
|
1337
|
+
outputSchema: z3.object({
|
|
1338
|
+
success: z3.boolean(),
|
|
1339
|
+
pricingOverview: z3.string().optional(),
|
|
1340
|
+
storageModel: z3.object({
|
|
1341
|
+
baseRate: z3.string(),
|
|
1342
|
+
minimumCharge: z3.string(),
|
|
1343
|
+
minimumThreshold: z3.string(),
|
|
1344
|
+
epochDuration: z3.string(),
|
|
1345
|
+
epochsPerDay: z3.number(),
|
|
1346
|
+
epochsPerMonth: z3.number()
|
|
1347
|
+
}).optional(),
|
|
1348
|
+
cdnModel: z3.object({
|
|
1349
|
+
egressRate: z3.string(),
|
|
1350
|
+
creditsTopUp: z3.string(),
|
|
1351
|
+
creditsPerUSDFC: z3.string(),
|
|
1352
|
+
isTopUpAFee: z3.boolean(),
|
|
1353
|
+
canTopUpMore: z3.boolean()
|
|
1354
|
+
}).optional(),
|
|
1355
|
+
exampleCalculation: z3.object({
|
|
1356
|
+
scenario: z3.string(),
|
|
1357
|
+
size: z3.string(),
|
|
1358
|
+
duration: z3.string(),
|
|
1359
|
+
monthlyCost: z3.string(),
|
|
1360
|
+
yearlyStorageCost: z3.string(),
|
|
1361
|
+
cdnTopUp: z3.string().optional(),
|
|
1362
|
+
cdnEgressCredits: z3.string().optional(),
|
|
1363
|
+
totalCost: z3.string(),
|
|
1364
|
+
breakdown: z3.string()
|
|
1365
|
+
}).optional(),
|
|
1366
|
+
paymentModel: z3.string().optional(),
|
|
1367
|
+
error: z3.string().optional(),
|
|
1368
|
+
message: z3.string()
|
|
1369
|
+
}),
|
|
1370
|
+
execute: async ({ context }) => {
|
|
1371
|
+
try {
|
|
1372
|
+
const { includeCDNExample } = context;
|
|
1373
|
+
const exampleSizeBytes = Number(SIZE_CONSTANTS4.TiB);
|
|
1374
|
+
const exampleDurationMonths = 12;
|
|
1375
|
+
const estimate = await estimateStorageCost(
|
|
1376
|
+
exampleSizeBytes,
|
|
1377
|
+
exampleDurationMonths,
|
|
1378
|
+
includeCDNExample
|
|
1379
|
+
);
|
|
1380
|
+
const cdnEgressCreditsGiB = includeCDNExample ? 1 / CDN_EGRESS_RATE_PER_TIB * 1024 : 0;
|
|
1381
|
+
const pricingOverview = [
|
|
1382
|
+
`FILECOIN ONCHAINCLOUD STORAGE PRICING`,
|
|
1383
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1384
|
+
``,
|
|
1385
|
+
`Filecoin OnchainCloud provides decentralized storage with transparent,`,
|
|
1386
|
+
`usage-based pricing. You only pay for what you store, when you store it.`,
|
|
1387
|
+
``,
|
|
1388
|
+
`COST COMPONENTS:`,
|
|
1389
|
+
` \u2022 Storage: Pay per epoch (30-second intervals) for stored data`,
|
|
1390
|
+
` \u2022 CDN Egress: Pay per TiB downloaded (optional, for fast retrieval)`,
|
|
1391
|
+
` \u2022 No hidden fees, no minimum contracts, no lock-in periods`,
|
|
1392
|
+
``
|
|
1393
|
+
].join("\n");
|
|
1394
|
+
const storageModel = {
|
|
1395
|
+
baseRate: "$2.50 per TiB per month",
|
|
1396
|
+
minimumCharge: "$0.06 per month",
|
|
1397
|
+
minimumThreshold: "~24.567 GiB (applies to storage smaller than this)",
|
|
1398
|
+
epochDuration: "30 seconds",
|
|
1399
|
+
epochsPerDay: 2880,
|
|
1400
|
+
epochsPerMonth: 86400
|
|
1401
|
+
// 30 days
|
|
1402
|
+
};
|
|
1403
|
+
const cdnModel = includeCDNExample ? {
|
|
1404
|
+
egressRate: "$7 per TiB downloaded (~$0.0068 per GiB)",
|
|
1405
|
+
creditsTopUp: "1 USDFC required when creating first CDN dataset",
|
|
1406
|
+
creditsPerUSDFC: "~146.29 GiB of egress credits",
|
|
1407
|
+
isTopUpAFee: false,
|
|
1408
|
+
canTopUpMore: true
|
|
1409
|
+
} : void 0;
|
|
1410
|
+
const exampleBreakdown = [
|
|
1411
|
+
`DETAILED CALCULATION:`,
|
|
1412
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1413
|
+
``,
|
|
1414
|
+
`Size: 1 TiB (1,099,511,627,776 bytes)`,
|
|
1415
|
+
`Duration: 12 months (1 year)`,
|
|
1416
|
+
``,
|
|
1417
|
+
`STORAGE COSTS:`,
|
|
1418
|
+
` Base Rate: $2.50/TiB/month`,
|
|
1419
|
+
` Calculation: 1 TiB \xD7 $2.50/month \xD7 12 months`,
|
|
1420
|
+
` Monthly Cost: ${(Number(estimate.monthlyCost) / 1e18).toFixed(4)} USDFC`,
|
|
1421
|
+
` Yearly Storage: ${(Number(estimate.totalCost - estimate.cdnSetupCost) / 1e18).toFixed(4)} USDFC`,
|
|
1422
|
+
``
|
|
1423
|
+
];
|
|
1424
|
+
if (includeCDNExample) {
|
|
1425
|
+
exampleBreakdown.push(
|
|
1426
|
+
`CDN EGRESS CREDITS (Optional):`,
|
|
1427
|
+
` Initial Top-up: 1.0 USDFC (pre-paid credits, NOT a fee)`,
|
|
1428
|
+
` Provides: ~${cdnEgressCreditsGiB.toFixed(2)} GiB of download credits`,
|
|
1429
|
+
` Reusing dataset: No additional top-up required`,
|
|
1430
|
+
` Additional top-ups: Available anytime`,
|
|
1431
|
+
``
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
exampleBreakdown.push(
|
|
1435
|
+
`TOTAL COST:`,
|
|
1436
|
+
` Storage (12 months): ${(Number(estimate.totalCost - estimate.cdnSetupCost) / 1e18).toFixed(4)} USDFC`
|
|
1437
|
+
);
|
|
1438
|
+
if (includeCDNExample) {
|
|
1439
|
+
exampleBreakdown.push(
|
|
1440
|
+
` CDN Credits Top-up: ${estimate.cdnSetupCostFormatted} USDFC`,
|
|
1441
|
+
` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
1442
|
+
` Grand Total: ${estimate.totalCostFormatted} USDFC`,
|
|
1443
|
+
``,
|
|
1444
|
+
`Note: The CDN top-up gives you ${cdnEgressCreditsGiB.toFixed(2)} GiB of downloads.`,
|
|
1445
|
+
` If you download your 1 TiB once, you'd need ~7 USDFC more in credits.`
|
|
1446
|
+
);
|
|
1447
|
+
} else {
|
|
1448
|
+
exampleBreakdown.push(
|
|
1449
|
+
` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
1450
|
+
` Total: ${estimate.totalCostFormatted} USDFC`
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
const paymentModel = [
|
|
1454
|
+
``,
|
|
1455
|
+
`HOW PAYMENT WORKS:`,
|
|
1456
|
+
`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
1457
|
+
``,
|
|
1458
|
+
`1. DEPOSIT FUNDS:`,
|
|
1459
|
+
` \u2022 Add USDFC tokens to your storage account`,
|
|
1460
|
+
` \u2022 Funds are held securely in your account`,
|
|
1461
|
+
` \u2022 You maintain full control and can withdraw anytime`,
|
|
1462
|
+
``,
|
|
1463
|
+
`2. SET ALLOWANCES (Safety Limits):`,
|
|
1464
|
+
` \u2022 Rate Allowance: Max spending per epoch (prevents runaway costs)`,
|
|
1465
|
+
` \u2022 Lockup Allowance: Max total funds that can be locked`,
|
|
1466
|
+
` \u2022 Max Lockup Period: Maximum duration for any payment rail`,
|
|
1467
|
+
``,
|
|
1468
|
+
`3. AUTOMATIC DEDUCTION:`,
|
|
1469
|
+
` \u2022 Every epoch (30 seconds), storage costs are calculated`,
|
|
1470
|
+
` \u2022 Amount is automatically deducted from your balance`,
|
|
1471
|
+
` \u2022 No manual payments, no invoices, fully automated`,
|
|
1472
|
+
``,
|
|
1473
|
+
`4. MONITORING:`,
|
|
1474
|
+
` \u2022 Check your balance anytime`,
|
|
1475
|
+
` \u2022 See your burn rate (spending per day/month)`,
|
|
1476
|
+
` \u2022 Get notified when balance is low`,
|
|
1477
|
+
` \u2022 Top up as needed to avoid service interruption`,
|
|
1478
|
+
``,
|
|
1479
|
+
`5. TRANSPARENCY:`,
|
|
1480
|
+
` \u2022 All costs calculated on-chain`,
|
|
1481
|
+
` \u2022 Real-time pricing information available`,
|
|
1482
|
+
` \u2022 No surprise charges or hidden fees`,
|
|
1483
|
+
``,
|
|
1484
|
+
`\u26A0\uFE0F CRITICAL - INSOLVENCY WARNING:`,
|
|
1485
|
+
` \u2022 Storage providers consider accounts with LESS THAN 30 DAYS of`,
|
|
1486
|
+
` remaining balance as INSOLVENT`,
|
|
1487
|
+
` \u2022 Insolvent accounts may be REFUSED SERVICE or have data removed`,
|
|
1488
|
+
` \u2022 ALWAYS maintain at least 45 days of balance for safety margin`,
|
|
1489
|
+
` \u2022 Default notification threshold: 45 days (gives time to deposit)`,
|
|
1490
|
+
` \u2022 Set up monitoring and alerts to avoid running low on funds`,
|
|
1491
|
+
``
|
|
1492
|
+
].join("\n");
|
|
1493
|
+
return {
|
|
1494
|
+
success: true,
|
|
1495
|
+
pricingOverview,
|
|
1496
|
+
storageModel,
|
|
1497
|
+
cdnModel,
|
|
1498
|
+
exampleCalculation: {
|
|
1499
|
+
scenario: "Storing 1 TiB for 1 year" + (includeCDNExample ? " with CDN" : ""),
|
|
1500
|
+
size: "1 TiB (1,099,511,627,776 bytes)",
|
|
1501
|
+
duration: "12 months (1 year)",
|
|
1502
|
+
monthlyCost: `${(Number(estimate.monthlyCost) / 1e18).toFixed(4)} USDFC`,
|
|
1503
|
+
yearlyStorageCost: `${(Number(estimate.totalCost - estimate.cdnSetupCost) / 1e18).toFixed(4)} USDFC`,
|
|
1504
|
+
cdnTopUp: includeCDNExample ? estimate.cdnSetupCostFormatted + " USDFC" : void 0,
|
|
1505
|
+
cdnEgressCredits: includeCDNExample ? `~${cdnEgressCreditsGiB.toFixed(2)} GiB of downloads` : void 0,
|
|
1506
|
+
totalCost: estimate.totalCostFormatted + " USDFC",
|
|
1507
|
+
breakdown: exampleBreakdown.join("\n")
|
|
1508
|
+
},
|
|
1509
|
+
paymentModel,
|
|
1510
|
+
message: `Storage pricing: $2.50/TiB/month (minimum $0.06/month). Example: 1 TiB for 1 year = ${estimate.totalCostFormatted} USDFC${includeCDNExample ? " (includes 1 USDFC CDN credits = ~" + cdnEgressCreditsGiB.toFixed(2) + " GiB downloads)" : ""}`
|
|
1511
|
+
};
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
return {
|
|
1514
|
+
success: false,
|
|
1515
|
+
error: error.message,
|
|
1516
|
+
message: `Failed to get pricing info: ${error.message}`
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
var storageCostTools = {
|
|
1522
|
+
estimateStoragePricing,
|
|
1523
|
+
convertStorageSize,
|
|
1524
|
+
getStoragePricingInfo
|
|
1525
|
+
};
|
|
1526
|
+
|
|
887
1527
|
// src/mastra/tools/index.ts
|
|
888
1528
|
var focStorageTools = {
|
|
889
1529
|
...datasetTools,
|
|
890
1530
|
...fileTools,
|
|
891
1531
|
...balanceTools,
|
|
892
1532
|
...paymentTools,
|
|
893
|
-
...providerTools
|
|
1533
|
+
...providerTools,
|
|
1534
|
+
...storageCostTools
|
|
894
1535
|
};
|
|
895
1536
|
var focStorageToolsArray = Object.values(focStorageTools);
|
|
896
1537
|
|
|
897
1538
|
// src/mastra/workflows/e2e-file-upload.ts
|
|
898
1539
|
import { createStep, createWorkflow } from "@mastra/core/workflows";
|
|
899
|
-
import { z as
|
|
900
|
-
var e2eFileUploadInputSchema =
|
|
901
|
-
filePath:
|
|
902
|
-
datasetId:
|
|
903
|
-
withCDN:
|
|
904
|
-
persistenceDays:
|
|
905
|
-
notificationThresholdDays:
|
|
906
|
-
fileMetadata:
|
|
1540
|
+
import { z as z4 } from "zod";
|
|
1541
|
+
var e2eFileUploadInputSchema = z4.object({
|
|
1542
|
+
filePath: z4.string().describe("Absolute path to the file to upload"),
|
|
1543
|
+
datasetId: z4.string().optional().describe("Existing dataset ID to use"),
|
|
1544
|
+
withCDN: z4.boolean().optional().describe("Enable CDN for faster retrieval").default(false),
|
|
1545
|
+
persistenceDays: z4.number().optional().describe("Storage duration in days (default: 180)").default(env.PERSISTENCE_PERIOD_DAYS),
|
|
1546
|
+
notificationThresholdDays: z4.number().optional().describe("Notification threshold in days (default: 10)").default(env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS),
|
|
1547
|
+
fileMetadata: z4.record(z4.string(), z4.string()).optional().describe("Metadata for the file (max 4 key-value pairs)")
|
|
907
1548
|
});
|
|
908
1549
|
var checkBalanceStep = createStep({
|
|
909
1550
|
id: "checkBalance",
|
|
910
1551
|
description: "Check current FIL/USDFC balances and storage metrics",
|
|
911
1552
|
inputSchema: e2eFileUploadInputSchema,
|
|
912
|
-
outputSchema:
|
|
913
|
-
balances:
|
|
914
|
-
needsPayment:
|
|
915
|
-
depositNeeded:
|
|
1553
|
+
outputSchema: z4.object({
|
|
1554
|
+
balances: z4.any(),
|
|
1555
|
+
needsPayment: z4.boolean(),
|
|
1556
|
+
depositNeeded: z4.number()
|
|
916
1557
|
}),
|
|
917
1558
|
execute: async ({ inputData, runtimeContext }) => {
|
|
918
1559
|
console.log("\u{1F4CA} STEP 1: Checking balances and storage metrics...");
|
|
@@ -939,16 +1580,16 @@ var checkBalanceStep = createStep({
|
|
|
939
1580
|
var processPaymentStep = createStep({
|
|
940
1581
|
id: "processPayment",
|
|
941
1582
|
description: "Deposit USDFC if insufficient balance detected",
|
|
942
|
-
inputSchema:
|
|
943
|
-
balances:
|
|
944
|
-
needsPayment:
|
|
945
|
-
depositNeeded:
|
|
1583
|
+
inputSchema: z4.object({
|
|
1584
|
+
balances: z4.any(),
|
|
1585
|
+
needsPayment: z4.boolean(),
|
|
1586
|
+
depositNeeded: z4.number()
|
|
946
1587
|
}),
|
|
947
|
-
outputSchema:
|
|
948
|
-
skipped:
|
|
949
|
-
txHash:
|
|
950
|
-
depositAmount:
|
|
951
|
-
message:
|
|
1588
|
+
outputSchema: z4.object({
|
|
1589
|
+
skipped: z4.boolean(),
|
|
1590
|
+
txHash: z4.string().optional(),
|
|
1591
|
+
depositAmount: z4.string().optional(),
|
|
1592
|
+
message: z4.string().optional()
|
|
952
1593
|
}),
|
|
953
1594
|
execute: async ({ getStepResult, getInitData, runtimeContext }) => {
|
|
954
1595
|
const balanceInfo = getStepResult("checkBalance");
|
|
@@ -999,18 +1640,18 @@ var processPaymentStep = createStep({
|
|
|
999
1640
|
var uploadFileStep = createStep({
|
|
1000
1641
|
id: "uploadFile",
|
|
1001
1642
|
description: "Upload file to Filecoin storage",
|
|
1002
|
-
inputSchema:
|
|
1003
|
-
skipped:
|
|
1004
|
-
txHash:
|
|
1005
|
-
depositAmount:
|
|
1006
|
-
message:
|
|
1643
|
+
inputSchema: z4.object({
|
|
1644
|
+
skipped: z4.boolean(),
|
|
1645
|
+
txHash: z4.string().optional(),
|
|
1646
|
+
depositAmount: z4.string().optional(),
|
|
1647
|
+
message: z4.string().optional()
|
|
1007
1648
|
}),
|
|
1008
|
-
outputSchema:
|
|
1009
|
-
pieceCid:
|
|
1010
|
-
txHash:
|
|
1011
|
-
fileName:
|
|
1012
|
-
fileSize:
|
|
1013
|
-
progressLog:
|
|
1649
|
+
outputSchema: z4.object({
|
|
1650
|
+
pieceCid: z4.string(),
|
|
1651
|
+
txHash: z4.string().optional(),
|
|
1652
|
+
fileName: z4.string(),
|
|
1653
|
+
fileSize: z4.number(),
|
|
1654
|
+
progressLog: z4.array(z4.string()).optional()
|
|
1014
1655
|
}),
|
|
1015
1656
|
execute: async ({ getInitData, runtimeContext }) => {
|
|
1016
1657
|
const initData = getInitData();
|
|
@@ -1047,19 +1688,19 @@ var uploadFileStep = createStep({
|
|
|
1047
1688
|
var summaryStep = createStep({
|
|
1048
1689
|
id: "summary",
|
|
1049
1690
|
description: "Generate final summary of the upload process",
|
|
1050
|
-
inputSchema:
|
|
1051
|
-
pieceCid:
|
|
1052
|
-
txHash:
|
|
1053
|
-
fileName:
|
|
1054
|
-
fileSize:
|
|
1055
|
-
progressLog:
|
|
1691
|
+
inputSchema: z4.object({
|
|
1692
|
+
pieceCid: z4.string(),
|
|
1693
|
+
txHash: z4.string().optional(),
|
|
1694
|
+
fileName: z4.string(),
|
|
1695
|
+
fileSize: z4.number(),
|
|
1696
|
+
progressLog: z4.array(z4.string()).optional()
|
|
1056
1697
|
}),
|
|
1057
|
-
outputSchema:
|
|
1058
|
-
success:
|
|
1059
|
-
summary:
|
|
1060
|
-
balance:
|
|
1061
|
-
payment:
|
|
1062
|
-
upload:
|
|
1698
|
+
outputSchema: z4.object({
|
|
1699
|
+
success: z4.boolean(),
|
|
1700
|
+
summary: z4.object({
|
|
1701
|
+
balance: z4.any(),
|
|
1702
|
+
payment: z4.any(),
|
|
1703
|
+
upload: z4.any()
|
|
1063
1704
|
})
|
|
1064
1705
|
}),
|
|
1065
1706
|
execute: async ({ getStepResult }) => {
|
|
@@ -1097,12 +1738,12 @@ var e2eFileUploadWorkflow = createWorkflow({
|
|
|
1097
1738
|
id: "e2eFileUpload",
|
|
1098
1739
|
description: "Upload a file to Filecoin storage",
|
|
1099
1740
|
inputSchema: e2eFileUploadInputSchema,
|
|
1100
|
-
outputSchema:
|
|
1101
|
-
success:
|
|
1102
|
-
summary:
|
|
1103
|
-
balance:
|
|
1104
|
-
payment:
|
|
1105
|
-
upload:
|
|
1741
|
+
outputSchema: z4.object({
|
|
1742
|
+
success: z4.boolean(),
|
|
1743
|
+
summary: z4.object({
|
|
1744
|
+
balance: z4.any(),
|
|
1745
|
+
payment: z4.any(),
|
|
1746
|
+
upload: z4.any()
|
|
1106
1747
|
})
|
|
1107
1748
|
})
|
|
1108
1749
|
}).then(checkBalanceStep).then(processPaymentStep).then(uploadFileStep).then(summaryStep).commit();
|
|
@@ -1137,6 +1778,7 @@ DATASET MANAGEMENT:
|
|
|
1137
1778
|
|
|
1138
1779
|
- Returns: Datasets with piece CIDs, file sizes, provider details, retrieval URLs, blockchain storage proofs
|
|
1139
1780
|
- Parameters: includeAllDatasets (boolean), filterByCDN (boolean)
|
|
1781
|
+
- Progress Tracking: Returns progressLog showing blockchain fetch and metadata processing steps
|
|
1140
1782
|
- Use when: User wants to inventory files, check storage status, or locate specific uploads
|
|
1141
1783
|
|
|
1142
1784
|
\u2022 getDataset: Retrieve detailed information about a specific dataset by its ID
|
|
@@ -1147,18 +1789,24 @@ DATASET MANAGEMENT:
|
|
|
1147
1789
|
|
|
1148
1790
|
\u2022 createDataset: Create a new dataset container on Filecoin for organizing related files
|
|
1149
1791
|
|
|
1150
|
-
- Parameters: withCDN (optional), providerId (
|
|
1792
|
+
- Parameters: withCDN (optional), providerId (required), metadata (up to 10 key-value pairs)
|
|
1151
1793
|
- Purpose: Define storage parameters (CDN, provider selection) that apply to all files added
|
|
1152
1794
|
- Benefits: Better file organization, consistent retrieval performance
|
|
1153
|
-
- Note: Payment is processed automatically for CDN-enabled datasets
|
|
1795
|
+
- Note: Payment is processed automatically for CDN-enabled datasets (1 USDFC)
|
|
1796
|
+
- Progress Tracking: Returns progressLog showing validation, CDN payment (if applicable), and dataset creation steps
|
|
1154
1797
|
- Use when: User wants dedicated dataset or specific storage configuration
|
|
1798
|
+
- Important: providerId is required - use getProviders to list available providers first
|
|
1155
1799
|
|
|
1156
1800
|
BALANCE & PAYMENT:
|
|
1157
1801
|
\u2022 getBalances: Check wallet balances (FIL and USDFC tokens) and comprehensive storage metrics
|
|
1158
1802
|
|
|
1159
1803
|
- Returns: Available funds, required deposits, days of storage remaining, allowance status
|
|
1160
|
-
- Output: Both human-readable formatted values and raw data
|
|
1161
|
-
- Parameters: storageCapacityBytes (optional), persistencePeriodDays (optional), notificationThresholdDays (optional)
|
|
1804
|
+
- Output: Both human-readable formatted values and raw data with progress log showing calculation parameters
|
|
1805
|
+
- Parameters: storageCapacityBytes (optional, default: 150 GiB), persistencePeriodDays (optional, default: 365 days), notificationThresholdDays (optional, default: 45 days)
|
|
1806
|
+
- Progress Log: Shows exact values used for calculations (capacity, persistence period, threshold)
|
|
1807
|
+
- \u26A0\uFE0F INSOLVENCY WARNING: Storage providers consider accounts with less than 30 days of remaining balance as INSOLVENT and may refuse service or remove data
|
|
1808
|
+
- Safety Margin: Default notification threshold is 45 days to ensure users have time to deposit before hitting the 30-day insolvency threshold
|
|
1809
|
+
- Agent Behavior: ALWAYS ask user before calling if they want default calculations or custom requirements. After showing results, ALWAYS offer to recalculate with different parameters. If days remaining is below 45, WARN user immediately about insolvency risk
|
|
1162
1810
|
- Use when: Before upload operations to verify sufficient balance, or to monitor storage budget and plan deposits
|
|
1163
1811
|
|
|
1164
1812
|
\u2022 processPayment: Deposit USDFC tokens and configure storage service allowances in a single transaction
|
|
@@ -1167,8 +1815,17 @@ BALANCE & PAYMENT:
|
|
|
1167
1815
|
- Parameters: depositAmount (optional, default: 0)
|
|
1168
1816
|
- Actions: Sets both rate allowance (per-epoch spending) and lockup allowance (total committed funds) to unlimited
|
|
1169
1817
|
- Validation: Checks wallet balance before processing to prevent failed transactions
|
|
1818
|
+
- Progress Tracking: Returns progressLog showing conversion, transaction initiation, and confirmation steps
|
|
1170
1819
|
- Use when: User needs to fund storage account before uploads or when balance is insufficient
|
|
1171
1820
|
|
|
1821
|
+
\u2022 processWithdrawal: Withdraw USDFC tokens from the storage account
|
|
1822
|
+
|
|
1823
|
+
- Parameters: withdrawalAmount (optional, default: 0)
|
|
1824
|
+
- Actions: Withdraws available funds from storage account back to wallet
|
|
1825
|
+
- Reduces storage service allowances and available balance
|
|
1826
|
+
- Progress Tracking: Returns progressLog showing conversion, transaction initiation, and confirmation steps
|
|
1827
|
+
- Use when: User wants to retrieve unused funds from storage account
|
|
1828
|
+
|
|
1172
1829
|
PROVIDER MANAGEMENT:
|
|
1173
1830
|
\u2022 getProviders: List storage providers available on the Filecoin network
|
|
1174
1831
|
|
|
@@ -1197,7 +1854,9 @@ PROVIDER MANAGEMENT:
|
|
|
1197
1854
|
|
|
1198
1855
|
5. MONITOR STORAGE METRICS AND PERSISTENCE:
|
|
1199
1856
|
- Check persistence days remaining regularly
|
|
1200
|
-
-
|
|
1857
|
+
- \u26A0\uFE0F CRITICAL: Maintain at least 45 days of balance (insolvency threshold is 30 days)
|
|
1858
|
+
- Storage providers will refuse service if balance falls below 30 days
|
|
1859
|
+
- Top up allowances before they run out to avoid service interruption and potential data loss
|
|
1201
1860
|
|
|
1202
1861
|
6. VALIDATE FILE PATHS:
|
|
1203
1862
|
- Ensure filePath is absolute path
|
|
@@ -1223,10 +1882,11 @@ FOR DATASET MANAGEMENT:
|
|
|
1223
1882
|
|
|
1224
1883
|
FOR BALANCE MANAGEMENT:
|
|
1225
1884
|
|
|
1226
|
-
1. Check Current State: getBalances
|
|
1227
|
-
2.
|
|
1228
|
-
3.
|
|
1229
|
-
4.
|
|
1885
|
+
1. Check Current State: getBalances to view current balances and storage metrics
|
|
1886
|
+
2. Evaluate Risk: If days remaining < 45, URGENT deposit needed (< 30 = INSOLVENT)
|
|
1887
|
+
3. Calculate Needs: Estimate storage requirements using getBalances output
|
|
1888
|
+
4. Process Payment: processPayment with appropriate depositAmount to maintain 45+ day buffer
|
|
1889
|
+
5. Verify: Check balances again to confirm deposit and ensure above insolvency threshold
|
|
1230
1890
|
|
|
1231
1891
|
\u{1F4A1} STRATEGIC CONSIDERATIONS:
|
|
1232
1892
|
|
|
@@ -1248,6 +1908,8 @@ COST MANAGEMENT:
|
|
|
1248
1908
|
\u2022 Rate allowance: Controls per-epoch spending
|
|
1249
1909
|
\u2022 Lockup allowance: Total committed for long-term storage
|
|
1250
1910
|
\u2022 Monitor both to avoid overspending or service interruption
|
|
1911
|
+
\u2022 \u26A0\uFE0F INSOLVENCY THRESHOLD: Keep balance above 30 days minimum (recommend 45+ days)
|
|
1912
|
+
\u2022 Providers refuse service below 30 days - plan deposits accordingly
|
|
1251
1913
|
|
|
1252
1914
|
\u{1F6A8} ERROR HANDLING:
|
|
1253
1915
|
|
|
@@ -1264,7 +1926,8 @@ DURING UPLOAD:
|
|
|
1264
1926
|
\u2022 Each phase has status updates
|
|
1265
1927
|
|
|
1266
1928
|
COMMON ERRORS:
|
|
1267
|
-
\u2022 "Insufficient tUSDFC balance": Need to deposit more USDFC \u2192 call processPayment
|
|
1929
|
+
\u2022 "Insufficient tUSDFC balance": Need to deposit more USDFC \u2192 call processPayment (ensure 45+ days buffer)
|
|
1930
|
+
\u2022 "Insolvency detected": Balance below 30 days \u2192 URGENT deposit required, providers may refuse service
|
|
1268
1931
|
\u2022 "Signer not found": Wallet not connected properly \u2192 check PRIVATE_KEY env var
|
|
1269
1932
|
\u2022 "Transaction failed": User rejected signature or gas issue \u2192 explain and retry
|
|
1270
1933
|
\u2022 "Provider connection failed": Try different provider or retry
|
|
@@ -1299,12 +1962,14 @@ ERROR RESPONSES:
|
|
|
1299
1962
|
\u{1F3AF} AGENT BEHAVIOR GUIDELINES:
|
|
1300
1963
|
|
|
1301
1964
|
1. BE PROACTIVE: Suggest checking balances before uploads
|
|
1302
|
-
2. BE
|
|
1303
|
-
3. BE
|
|
1304
|
-
4. BE
|
|
1305
|
-
5. BE
|
|
1306
|
-
6. BE
|
|
1307
|
-
7. BE
|
|
1965
|
+
2. BE VIGILANT: ALWAYS warn if balance days remaining < 45 (insolvency risk at < 30)
|
|
1966
|
+
3. BE CLEAR: Explain blockchain concepts simply
|
|
1967
|
+
4. BE PATIENT: Uploads take time (30-60 seconds typical)
|
|
1968
|
+
5. BE HELPFUL: Guide users through wallet signatures
|
|
1969
|
+
6. BE ACCURATE: Provide precise pieceCids and txHashes
|
|
1970
|
+
7. BE EFFICIENT: Reuse datasets when appropriate
|
|
1971
|
+
8. BE SECURE: Never store sensitive data without user confirmation
|
|
1972
|
+
9. BE URGENT: Treat insolvency warnings as critical - emphasize immediate action needed
|
|
1308
1973
|
|
|
1309
1974
|
\u{1F510} SECURITY CONSIDERATIONS:
|
|
1310
1975
|
\u2022 Never expose private keys or wallet seeds
|
|
@@ -1357,7 +2022,7 @@ var focStorageResources = {
|
|
|
1357
2022
|
// src/mastra/index.ts
|
|
1358
2023
|
var mcpServer = new MCPServer({
|
|
1359
2024
|
name: "FOC Storage MCP",
|
|
1360
|
-
version: "0.1.
|
|
2025
|
+
version: "0.1.3",
|
|
1361
2026
|
description: "Professional-grade MCP server for decentralized file storage on Filecoin Onchain Cloud. Powered by the FOC-Synapse SDK, this server provides AI agents with seamless access to Filecoin's distributed storage network. Upload files with automatic payment handling, organize content in datasets, monitor storage balances, and manage providers - all through intuitive MCP tools. Supports both standard storage and CDN-enabled fast retrieval. Perfect for building AI applications that need persistent, censorship-resistant storage.",
|
|
1362
2027
|
tools: focStorageTools,
|
|
1363
2028
|
repository: {
|