@fil-b/foc-storage-mcp 0.1.1 → 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 +1097 -427
- 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,180 +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
|
-
return `${serviceURL}/piece/${pieceCid}`;
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
const datasetSizeInfo = data.pieces.reduce((acc, piece) => {
|
|
411
|
-
acc.sizeInBytes += Number(pieces[piece.pieceCid.toV1().toString()].sizeBytes);
|
|
412
|
-
acc.sizeInKiB += Number(pieces[piece.pieceCid.toV1().toString()].sizeKiB);
|
|
413
|
-
acc.sizeInMiB += Number(pieces[piece.pieceCid.toV1().toString()].sizeMiB);
|
|
414
|
-
acc.sizeInGB += Number(pieces[piece.pieceCid.toV1().toString()].sizeGiB);
|
|
415
|
-
return acc;
|
|
416
|
-
}, { sizeInBytes: 0, sizeInKiB: 0, sizeInMiB: 0, sizeInGB: 0, message: "" });
|
|
417
|
-
const dataSetPieces = data.pieces.map((piece) => ({
|
|
418
|
-
pieceCid: piece.pieceCid.toV1().toString(),
|
|
419
|
-
retrievalUrl: getRetrievalUrl(piece.pieceCid.toV1().toString()),
|
|
420
|
-
sizes: pieces[piece.pieceCid.toV1().toString()],
|
|
421
|
-
metadata: piecesMetadata[piece.pieceId]
|
|
422
|
-
}));
|
|
423
|
-
return {
|
|
424
|
-
datasetId: dataset.pdpVerifierDataSetId,
|
|
425
|
-
withCDN: dataset.withCDN,
|
|
426
|
-
datasetMetadata: dataset.metadata,
|
|
427
|
-
totalDatasetSizeMessage: sizeInfoMessage(datasetSizeInfo),
|
|
428
|
-
dataSetPieces
|
|
429
|
-
};
|
|
430
|
-
} catch (error) {
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
})
|
|
434
|
-
);
|
|
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 });
|
|
435
451
|
return {
|
|
436
452
|
success: true,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
message:
|
|
453
|
+
dataSetId: dataSetId.toString(),
|
|
454
|
+
txHash: dataset.txHash,
|
|
455
|
+
message: "Dataset created successfully"
|
|
440
456
|
};
|
|
441
457
|
} catch (error) {
|
|
442
|
-
return
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
458
|
+
return {
|
|
459
|
+
success: false,
|
|
460
|
+
txHash: null,
|
|
461
|
+
dataSetId: null,
|
|
462
|
+
message: "Failed to create dataset: " + synapseErrorHandler(error)
|
|
463
|
+
};
|
|
447
464
|
}
|
|
448
465
|
};
|
|
449
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
|
+
|
|
450
505
|
// src/services/storage-service.ts
|
|
451
506
|
import {
|
|
452
|
-
SIZE_CONSTANTS as
|
|
453
|
-
TIME_CONSTANTS
|
|
454
|
-
|
|
455
|
-
WarmStorageService as WarmStorageService2
|
|
456
|
-
} from "@filoz/synapse-sdk";
|
|
507
|
+
SIZE_CONSTANTS as SIZE_CONSTANTS3,
|
|
508
|
+
TIME_CONSTANTS
|
|
509
|
+
} from "@filoz/synapse-core/utils";
|
|
457
510
|
import { formatUnits } from "viem";
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
})
|
|
464
542
|
]);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
} catch (error) {
|
|
469
|
-
console.error(error);
|
|
470
|
-
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.");
|
|
471
|
-
}
|
|
472
|
-
let usdfcRaw = await synapse.payments.walletBalance(TOKENS2.USDFC);
|
|
473
|
-
const allowance = storageInfo.allowances;
|
|
474
|
-
const availableFunds = accountInfo.availableFunds;
|
|
475
|
-
const currentMonthlyRate = allowance.rateUsed * TIME_CONSTANTS3.EPOCHS_PER_MONTH;
|
|
476
|
-
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;
|
|
477
546
|
const daysLeftAtMaxBurnRate = maxMonthlyRate === 0n ? Infinity : Number(availableFunds) / Number(maxMonthlyRate) * 30;
|
|
478
547
|
const daysLeftAtBurnRate = currentMonthlyRate === 0n ? Infinity : Number(availableFunds) / Number(currentMonthlyRate) * 30;
|
|
479
|
-
const amountNeeded =
|
|
480
|
-
const totalDepositNeeded = daysLeftAtMaxBurnRate >= env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS ? 0n : amountNeeded -
|
|
481
|
-
const availableToFreeUp =
|
|
482
|
-
const isRateSufficient =
|
|
483
|
-
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;
|
|
484
553
|
const isSufficient = isRateSufficient && isLockupSufficient && daysLeftAtMaxBurnRate >= env.RUNOUT_NOTIFICATION_THRESHOLD_DAYS;
|
|
485
554
|
return {
|
|
486
555
|
filBalance: filRaw,
|
|
@@ -513,8 +582,8 @@ var formatStorageBalanceResult = (checkStorageBalanceResult) => {
|
|
|
513
582
|
isSufficient: checkStorageBalanceResult.isSufficient
|
|
514
583
|
};
|
|
515
584
|
};
|
|
516
|
-
var formatBalance = (
|
|
517
|
-
return `${Number(Number(formatUnits(
|
|
585
|
+
var formatBalance = (balance2, ticker) => {
|
|
586
|
+
return `${Number(Number(formatUnits(balance2, 18)).toFixed(8))} ${ticker}`;
|
|
518
587
|
};
|
|
519
588
|
var formatTime = (days) => {
|
|
520
589
|
if (days === Infinity) {
|
|
@@ -531,17 +600,89 @@ var formatTime = (days) => {
|
|
|
531
600
|
}
|
|
532
601
|
return `${Math.fround(days / 365)} years`;
|
|
533
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
|
+
};
|
|
534
650
|
|
|
535
651
|
// src/mastra/tools/dataset-tools.ts
|
|
536
|
-
var
|
|
652
|
+
var getDatasets = createTool({
|
|
537
653
|
id: "getDatasets",
|
|
538
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.",
|
|
539
655
|
inputSchema: GetDatasetsSchema,
|
|
540
656
|
outputSchema: GetDatasetsOutputSchema,
|
|
541
657
|
execute: async ({ context }) => {
|
|
542
|
-
const
|
|
543
|
-
const
|
|
544
|
-
|
|
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
|
+
}
|
|
545
686
|
}
|
|
546
687
|
});
|
|
547
688
|
var getDataset = createTool({
|
|
@@ -551,11 +692,11 @@ var getDataset = createTool({
|
|
|
551
692
|
outputSchema: GetDatasetOutputSchema,
|
|
552
693
|
execute: async ({ context }) => {
|
|
553
694
|
try {
|
|
554
|
-
const dataset = await
|
|
695
|
+
const dataset = await getDataSetService(Number(context.datasetId));
|
|
555
696
|
return {
|
|
556
|
-
success:
|
|
557
|
-
dataset: serializeBigInt(dataset
|
|
558
|
-
message:
|
|
697
|
+
success: true,
|
|
698
|
+
dataset: serializeBigInt(dataset),
|
|
699
|
+
message: "Dataset fetched successfully"
|
|
559
700
|
};
|
|
560
701
|
} catch (error) {
|
|
561
702
|
return createErrorResponse(
|
|
@@ -568,60 +709,60 @@ var getDataset = createTool({
|
|
|
568
709
|
});
|
|
569
710
|
var createDataset = createTool({
|
|
570
711
|
id: "createDataset",
|
|
571
|
-
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.",
|
|
572
713
|
inputSchema: CreateDatasetSchema,
|
|
573
714
|
outputSchema: CreateDatasetOutputSchema,
|
|
574
715
|
execute: async ({ context }) => {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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)}`);
|
|
587
751
|
}
|
|
588
|
-
let datasetId;
|
|
589
|
-
let txHash;
|
|
590
|
-
await synapse.createStorage({
|
|
591
|
-
providerId: context.providerId ? parseInt(context.providerId) : void 0,
|
|
592
|
-
forceCreateDataSet: true,
|
|
593
|
-
metadata: context.metadata,
|
|
594
|
-
callbacks: {
|
|
595
|
-
onDataSetCreationStarted: (txResponse) => {
|
|
596
|
-
txHash = txResponse.hash;
|
|
597
|
-
console.log(`[Dataset] Creation started (tx: ${txResponse.hash})`);
|
|
598
|
-
},
|
|
599
|
-
onDataSetCreationProgress: (status) => {
|
|
600
|
-
if (status.serverConfirmed) {
|
|
601
|
-
datasetId = status.dataSetId?.toString() || void 0;
|
|
602
|
-
console.log(`[Dataset] Ready (ID: ${status.dataSetId?.toString()})`);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
752
|
return {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
txHash,
|
|
611
|
-
message: "Dataset created successfully"
|
|
753
|
+
...result,
|
|
754
|
+
progressLog
|
|
612
755
|
};
|
|
613
|
-
} catch (error) {
|
|
614
|
-
return createErrorResponse(
|
|
615
|
-
"dataset_creation_failed",
|
|
616
|
-
`Failed to create dataset: ${error.message}`,
|
|
617
|
-
{ success: false }
|
|
618
|
-
);
|
|
619
756
|
}
|
|
757
|
+
return {
|
|
758
|
+
...result,
|
|
759
|
+
progressLog
|
|
760
|
+
};
|
|
620
761
|
}
|
|
621
762
|
});
|
|
622
763
|
var datasetTools = {
|
|
623
764
|
getDataset,
|
|
624
|
-
getDatasets
|
|
765
|
+
getDatasets,
|
|
625
766
|
createDataset
|
|
626
767
|
};
|
|
627
768
|
|
|
@@ -651,7 +792,6 @@ var uploadFile = createTool2({
|
|
|
651
792
|
const synapse = await getSynapseInstance();
|
|
652
793
|
log("Checking storage balance...");
|
|
653
794
|
const storageMetrics = await checkStorageBalance(
|
|
654
|
-
synapse,
|
|
655
795
|
fileInfo.size,
|
|
656
796
|
env.PERSISTENCE_PERIOD_DAYS
|
|
657
797
|
);
|
|
@@ -660,22 +800,25 @@ var uploadFile = createTool2({
|
|
|
660
800
|
return {
|
|
661
801
|
success: false,
|
|
662
802
|
error: "insufficient_balance",
|
|
663
|
-
message:
|
|
664
|
-
required: {
|
|
665
|
-
deposit: fromBaseUnits(storageMetrics.depositNeeded, 18)
|
|
666
|
-
}
|
|
803
|
+
message: `Insufficient balance: ${fromBaseUnits(storageMetrics.depositNeeded, 18)} USDFC required. Enable autoPayment parameter or use processPayment tool to deposit funds first.`
|
|
667
804
|
};
|
|
668
805
|
}
|
|
669
806
|
log(storageMetrics.depositNeeded > 0n ? "Insufficient balance, processing payment..." : "Insufficient service approvals, approving service...");
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
storageMetrics.depositNeeded
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
+
}
|
|
676
819
|
}
|
|
677
820
|
log("Creating storage service...");
|
|
678
|
-
const storageService = await synapse.
|
|
821
|
+
const storageService = await synapse.storage.createContext({
|
|
679
822
|
dataSetId: context.datasetId ? Number(context.datasetId) : void 0,
|
|
680
823
|
withCDN: context.withCDN || false,
|
|
681
824
|
callbacks: {
|
|
@@ -684,16 +827,8 @@ var uploadFile = createTool2({
|
|
|
684
827
|
log(`Dataset provider: ${Number(info.provider.id)}`);
|
|
685
828
|
log(`Is existing dataset: ${info.isExisting}`);
|
|
686
829
|
},
|
|
687
|
-
|
|
688
|
-
log(`
|
|
689
|
-
},
|
|
690
|
-
onDataSetCreationProgress: (status) => {
|
|
691
|
-
if (status.serverConfirmed) {
|
|
692
|
-
log(`Dataset ready (ID: ${status.dataSetId})`);
|
|
693
|
-
}
|
|
694
|
-
},
|
|
695
|
-
onProviderSelected: (provider) => {
|
|
696
|
-
log(`Provider selected: ${provider.id}`);
|
|
830
|
+
onProviderSelected: (provider2) => {
|
|
831
|
+
log(`Provider selected: ${provider2.id}`);
|
|
697
832
|
}
|
|
698
833
|
}
|
|
699
834
|
});
|
|
@@ -704,19 +839,24 @@ var uploadFile = createTool2({
|
|
|
704
839
|
onUploadComplete: (piece) => {
|
|
705
840
|
log(`Upload complete (pieceCid: ${piece.toV1().toString()})`);
|
|
706
841
|
},
|
|
707
|
-
onPieceAdded: (
|
|
708
|
-
uploadTxHash =
|
|
709
|
-
log(`Piece added to dataset (tx: ${
|
|
842
|
+
onPieceAdded: (hash) => {
|
|
843
|
+
uploadTxHash = hash;
|
|
844
|
+
log(`Piece added to dataset (tx: ${hash || "pending"})`);
|
|
710
845
|
},
|
|
711
846
|
onPieceConfirmed: () => {
|
|
712
847
|
log("Piece confirmed on blockchain");
|
|
713
848
|
}
|
|
714
849
|
});
|
|
850
|
+
const provider = await getProvider(storageService.provider.id);
|
|
715
851
|
const getRetrievalUrl = async (pieceCid2) => {
|
|
716
852
|
if (context.withCDN) {
|
|
717
|
-
|
|
853
|
+
const network = env.FILECOIN_NETWORK === "mainnet" ? "mainnet" : "calibration";
|
|
854
|
+
return `https://${await synapse.getSigner().getAddress()}.${network}.filbeam.io/${pieceCid2}`;
|
|
718
855
|
} else {
|
|
719
|
-
|
|
856
|
+
const serviceURL = provider.pdp.serviceURL;
|
|
857
|
+
const endsWithSlash = serviceURL.endsWith("/");
|
|
858
|
+
const serviceURLWithoutSlash = endsWithSlash ? serviceURL.slice(0, -1) : serviceURL;
|
|
859
|
+
return `${serviceURLWithoutSlash}/piece/${pieceCid2}`;
|
|
720
860
|
}
|
|
721
861
|
};
|
|
722
862
|
const retrievalUrl = await getRetrievalUrl(pieceCid.toV1().toString());
|
|
@@ -725,6 +865,9 @@ var uploadFile = createTool2({
|
|
|
725
865
|
console.log(`TX Hash: ${uploadTxHash}`);
|
|
726
866
|
console.log(`File Name: ${fileName}`);
|
|
727
867
|
console.log(`File Size: ${fileInfo.size}`);
|
|
868
|
+
if (uploadTxHash) {
|
|
869
|
+
log(`View transaction: ${getExplorerUrl(uploadTxHash)}`);
|
|
870
|
+
}
|
|
728
871
|
return {
|
|
729
872
|
success: true,
|
|
730
873
|
pieceCid: pieceCid.toV1().toString(),
|
|
@@ -755,24 +898,53 @@ var fileTools = {
|
|
|
755
898
|
import { createTool as createTool3 } from "@mastra/core";
|
|
756
899
|
var getBalances = createTool3({
|
|
757
900
|
id: "getBalances",
|
|
758
|
-
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.",
|
|
759
902
|
inputSchema: GetBalancesSchema,
|
|
760
903
|
outputSchema: GetBalancesOutputSchema,
|
|
761
904
|
execute: async ({ context }) => {
|
|
905
|
+
const progressLog = [];
|
|
906
|
+
const log = (msg) => {
|
|
907
|
+
progressLog.push(msg);
|
|
908
|
+
};
|
|
762
909
|
try {
|
|
763
|
-
const
|
|
764
|
-
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.");
|
|
765
934
|
return {
|
|
766
935
|
success: true,
|
|
767
|
-
checkStorageBalanceResultFormatted:
|
|
768
|
-
checkStorageBalanceResult:
|
|
936
|
+
checkStorageBalanceResultFormatted: formattedResult,
|
|
937
|
+
checkStorageBalanceResult: serializedResult,
|
|
938
|
+
progressLog,
|
|
939
|
+
message: `Balance check complete for ${capacityTB} TB over ${persistencePeriodDays} days. Available: ${formattedResult.usdfcBalance}`
|
|
769
940
|
};
|
|
770
941
|
} catch (error) {
|
|
771
|
-
return
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
942
|
+
return {
|
|
943
|
+
success: false,
|
|
944
|
+
error: "balance_fetch_failed",
|
|
945
|
+
message: `Failed to fetch balances: ${error.message}`,
|
|
946
|
+
progressLog
|
|
947
|
+
};
|
|
776
948
|
}
|
|
777
949
|
}
|
|
778
950
|
});
|
|
@@ -784,61 +956,82 @@ var balanceTools = {
|
|
|
784
956
|
import { createTool as createTool4 } from "@mastra/core";
|
|
785
957
|
var processPayment = createTool4({
|
|
786
958
|
id: "processPayment",
|
|
787
|
-
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.",
|
|
788
960
|
inputSchema: ProcessPaymentSchema,
|
|
789
961
|
outputSchema: ProcessPaymentOutputSchema,
|
|
790
962
|
execute: async ({ context }) => {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
required: {
|
|
802
|
-
deposit: depositAmount
|
|
803
|
-
},
|
|
804
|
-
available: availableFunds
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
if (availableFunds < depositAmount) {
|
|
808
|
-
return {
|
|
809
|
-
success: false,
|
|
810
|
-
error: "insufficient_balance",
|
|
811
|
-
message: `Insufficient USDFC balance. Required: ${depositAmount}, Available: ${availableFunds}`,
|
|
812
|
-
required: depositAmount,
|
|
813
|
-
available: Number(availableFunds)
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
const result = await processStoragePayment(
|
|
817
|
-
synapse,
|
|
818
|
-
BigInt(depositAmount),
|
|
819
|
-
env.PERSISTENCE_PERIOD_DAYS
|
|
820
|
-
);
|
|
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) {
|
|
821
973
|
return {
|
|
822
|
-
success:
|
|
823
|
-
|
|
824
|
-
message: `
|
|
974
|
+
success: false,
|
|
975
|
+
error: "payment_failed",
|
|
976
|
+
message: `Failed to process payment: ${error}`,
|
|
977
|
+
progressLog
|
|
825
978
|
};
|
|
826
|
-
} catch (error) {
|
|
827
|
-
return createErrorResponse(
|
|
828
|
-
"payment_failed",
|
|
829
|
-
`Payment processing failed: ${error.message}`,
|
|
830
|
-
{ success: false }
|
|
831
|
-
);
|
|
832
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
|
+
};
|
|
833
1025
|
}
|
|
834
1026
|
});
|
|
835
1027
|
var paymentTools = {
|
|
836
|
-
processPayment
|
|
1028
|
+
processPayment,
|
|
1029
|
+
processWithdrawal
|
|
837
1030
|
};
|
|
838
1031
|
|
|
839
1032
|
// src/mastra/tools/provider-tools.ts
|
|
840
1033
|
import { createTool as createTool5 } from "@mastra/core";
|
|
841
|
-
import {
|
|
1034
|
+
import { readProviders as readProviders2 } from "@filoz/synapse-core/warm-storage";
|
|
842
1035
|
var getProviders = createTool5({
|
|
843
1036
|
id: "getProviders",
|
|
844
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.",
|
|
@@ -846,21 +1039,11 @@ var getProviders = createTool5({
|
|
|
846
1039
|
outputSchema: GetProvidersOutputSchema,
|
|
847
1040
|
execute: async ({ context }) => {
|
|
848
1041
|
try {
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1042
|
+
const providers = (await readProviders2(
|
|
1043
|
+
publicClient
|
|
1044
|
+
)).filter(
|
|
1045
|
+
(p) => context.onlyApproved ? p.isActive : true
|
|
853
1046
|
);
|
|
854
|
-
const approvedProviderIds = await warmStorageService.getApprovedProviderIds();
|
|
855
|
-
const providersInfo = await Promise.all(
|
|
856
|
-
approvedProviderIds.map(async (providerId) => {
|
|
857
|
-
const providerInfo = await synapse.getProviderInfo(providerId);
|
|
858
|
-
return providerInfo;
|
|
859
|
-
})
|
|
860
|
-
);
|
|
861
|
-
const providers = context.onlyApproved !== false ? providersInfo.filter(
|
|
862
|
-
(p) => approvedProviderIds.includes(p.id)
|
|
863
|
-
) : providersInfo;
|
|
864
1047
|
return {
|
|
865
1048
|
success: true,
|
|
866
1049
|
providers: providers.map(serializeBigInt),
|
|
@@ -868,10 +1051,12 @@ var getProviders = createTool5({
|
|
|
868
1051
|
message: `Found ${providers.length} provider(s)`
|
|
869
1052
|
};
|
|
870
1053
|
} catch (error) {
|
|
871
|
-
return
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1054
|
+
return {
|
|
1055
|
+
success: false,
|
|
1056
|
+
providers: [],
|
|
1057
|
+
error: error.message,
|
|
1058
|
+
message: `Failed to fetch providers: ${error.message}`
|
|
1059
|
+
};
|
|
875
1060
|
}
|
|
876
1061
|
}
|
|
877
1062
|
});
|
|
@@ -879,35 +1064,496 @@ var providerTools = {
|
|
|
879
1064
|
getProviders
|
|
880
1065
|
};
|
|
881
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
|
+
|
|
882
1527
|
// src/mastra/tools/index.ts
|
|
883
1528
|
var focStorageTools = {
|
|
884
1529
|
...datasetTools,
|
|
885
1530
|
...fileTools,
|
|
886
1531
|
...balanceTools,
|
|
887
1532
|
...paymentTools,
|
|
888
|
-
...providerTools
|
|
1533
|
+
...providerTools,
|
|
1534
|
+
...storageCostTools
|
|
889
1535
|
};
|
|
890
1536
|
var focStorageToolsArray = Object.values(focStorageTools);
|
|
891
1537
|
|
|
892
1538
|
// src/mastra/workflows/e2e-file-upload.ts
|
|
893
1539
|
import { createStep, createWorkflow } from "@mastra/core/workflows";
|
|
894
|
-
import { z as
|
|
895
|
-
var e2eFileUploadInputSchema =
|
|
896
|
-
filePath:
|
|
897
|
-
datasetId:
|
|
898
|
-
withCDN:
|
|
899
|
-
persistenceDays:
|
|
900
|
-
notificationThresholdDays:
|
|
901
|
-
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)")
|
|
902
1548
|
});
|
|
903
1549
|
var checkBalanceStep = createStep({
|
|
904
1550
|
id: "checkBalance",
|
|
905
1551
|
description: "Check current FIL/USDFC balances and storage metrics",
|
|
906
1552
|
inputSchema: e2eFileUploadInputSchema,
|
|
907
|
-
outputSchema:
|
|
908
|
-
balances:
|
|
909
|
-
needsPayment:
|
|
910
|
-
depositNeeded:
|
|
1553
|
+
outputSchema: z4.object({
|
|
1554
|
+
balances: z4.any(),
|
|
1555
|
+
needsPayment: z4.boolean(),
|
|
1556
|
+
depositNeeded: z4.number()
|
|
911
1557
|
}),
|
|
912
1558
|
execute: async ({ inputData, runtimeContext }) => {
|
|
913
1559
|
console.log("\u{1F4CA} STEP 1: Checking balances and storage metrics...");
|
|
@@ -934,16 +1580,16 @@ var checkBalanceStep = createStep({
|
|
|
934
1580
|
var processPaymentStep = createStep({
|
|
935
1581
|
id: "processPayment",
|
|
936
1582
|
description: "Deposit USDFC if insufficient balance detected",
|
|
937
|
-
inputSchema:
|
|
938
|
-
balances:
|
|
939
|
-
needsPayment:
|
|
940
|
-
depositNeeded:
|
|
1583
|
+
inputSchema: z4.object({
|
|
1584
|
+
balances: z4.any(),
|
|
1585
|
+
needsPayment: z4.boolean(),
|
|
1586
|
+
depositNeeded: z4.number()
|
|
941
1587
|
}),
|
|
942
|
-
outputSchema:
|
|
943
|
-
skipped:
|
|
944
|
-
txHash:
|
|
945
|
-
depositAmount:
|
|
946
|
-
message:
|
|
1588
|
+
outputSchema: z4.object({
|
|
1589
|
+
skipped: z4.boolean(),
|
|
1590
|
+
txHash: z4.string().optional(),
|
|
1591
|
+
depositAmount: z4.string().optional(),
|
|
1592
|
+
message: z4.string().optional()
|
|
947
1593
|
}),
|
|
948
1594
|
execute: async ({ getStepResult, getInitData, runtimeContext }) => {
|
|
949
1595
|
const balanceInfo = getStepResult("checkBalance");
|
|
@@ -994,18 +1640,18 @@ var processPaymentStep = createStep({
|
|
|
994
1640
|
var uploadFileStep = createStep({
|
|
995
1641
|
id: "uploadFile",
|
|
996
1642
|
description: "Upload file to Filecoin storage",
|
|
997
|
-
inputSchema:
|
|
998
|
-
skipped:
|
|
999
|
-
txHash:
|
|
1000
|
-
depositAmount:
|
|
1001
|
-
message:
|
|
1643
|
+
inputSchema: z4.object({
|
|
1644
|
+
skipped: z4.boolean(),
|
|
1645
|
+
txHash: z4.string().optional(),
|
|
1646
|
+
depositAmount: z4.string().optional(),
|
|
1647
|
+
message: z4.string().optional()
|
|
1002
1648
|
}),
|
|
1003
|
-
outputSchema:
|
|
1004
|
-
pieceCid:
|
|
1005
|
-
txHash:
|
|
1006
|
-
fileName:
|
|
1007
|
-
fileSize:
|
|
1008
|
-
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()
|
|
1009
1655
|
}),
|
|
1010
1656
|
execute: async ({ getInitData, runtimeContext }) => {
|
|
1011
1657
|
const initData = getInitData();
|
|
@@ -1042,19 +1688,19 @@ var uploadFileStep = createStep({
|
|
|
1042
1688
|
var summaryStep = createStep({
|
|
1043
1689
|
id: "summary",
|
|
1044
1690
|
description: "Generate final summary of the upload process",
|
|
1045
|
-
inputSchema:
|
|
1046
|
-
pieceCid:
|
|
1047
|
-
txHash:
|
|
1048
|
-
fileName:
|
|
1049
|
-
fileSize:
|
|
1050
|
-
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()
|
|
1051
1697
|
}),
|
|
1052
|
-
outputSchema:
|
|
1053
|
-
success:
|
|
1054
|
-
summary:
|
|
1055
|
-
balance:
|
|
1056
|
-
payment:
|
|
1057
|
-
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()
|
|
1058
1704
|
})
|
|
1059
1705
|
}),
|
|
1060
1706
|
execute: async ({ getStepResult }) => {
|
|
@@ -1092,12 +1738,12 @@ var e2eFileUploadWorkflow = createWorkflow({
|
|
|
1092
1738
|
id: "e2eFileUpload",
|
|
1093
1739
|
description: "Upload a file to Filecoin storage",
|
|
1094
1740
|
inputSchema: e2eFileUploadInputSchema,
|
|
1095
|
-
outputSchema:
|
|
1096
|
-
success:
|
|
1097
|
-
summary:
|
|
1098
|
-
balance:
|
|
1099
|
-
payment:
|
|
1100
|
-
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()
|
|
1101
1747
|
})
|
|
1102
1748
|
})
|
|
1103
1749
|
}).then(checkBalanceStep).then(processPaymentStep).then(uploadFileStep).then(summaryStep).commit();
|
|
@@ -1132,6 +1778,7 @@ DATASET MANAGEMENT:
|
|
|
1132
1778
|
|
|
1133
1779
|
- Returns: Datasets with piece CIDs, file sizes, provider details, retrieval URLs, blockchain storage proofs
|
|
1134
1780
|
- Parameters: includeAllDatasets (boolean), filterByCDN (boolean)
|
|
1781
|
+
- Progress Tracking: Returns progressLog showing blockchain fetch and metadata processing steps
|
|
1135
1782
|
- Use when: User wants to inventory files, check storage status, or locate specific uploads
|
|
1136
1783
|
|
|
1137
1784
|
\u2022 getDataset: Retrieve detailed information about a specific dataset by its ID
|
|
@@ -1142,18 +1789,24 @@ DATASET MANAGEMENT:
|
|
|
1142
1789
|
|
|
1143
1790
|
\u2022 createDataset: Create a new dataset container on Filecoin for organizing related files
|
|
1144
1791
|
|
|
1145
|
-
- Parameters: withCDN (optional), providerId (
|
|
1792
|
+
- Parameters: withCDN (optional), providerId (required), metadata (up to 10 key-value pairs)
|
|
1146
1793
|
- Purpose: Define storage parameters (CDN, provider selection) that apply to all files added
|
|
1147
1794
|
- Benefits: Better file organization, consistent retrieval performance
|
|
1148
|
-
- 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
|
|
1149
1797
|
- Use when: User wants dedicated dataset or specific storage configuration
|
|
1798
|
+
- Important: providerId is required - use getProviders to list available providers first
|
|
1150
1799
|
|
|
1151
1800
|
BALANCE & PAYMENT:
|
|
1152
1801
|
\u2022 getBalances: Check wallet balances (FIL and USDFC tokens) and comprehensive storage metrics
|
|
1153
1802
|
|
|
1154
1803
|
- Returns: Available funds, required deposits, days of storage remaining, allowance status
|
|
1155
|
-
- Output: Both human-readable formatted values and raw data
|
|
1156
|
-
- 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
|
|
1157
1810
|
- Use when: Before upload operations to verify sufficient balance, or to monitor storage budget and plan deposits
|
|
1158
1811
|
|
|
1159
1812
|
\u2022 processPayment: Deposit USDFC tokens and configure storage service allowances in a single transaction
|
|
@@ -1162,8 +1815,17 @@ BALANCE & PAYMENT:
|
|
|
1162
1815
|
- Parameters: depositAmount (optional, default: 0)
|
|
1163
1816
|
- Actions: Sets both rate allowance (per-epoch spending) and lockup allowance (total committed funds) to unlimited
|
|
1164
1817
|
- Validation: Checks wallet balance before processing to prevent failed transactions
|
|
1818
|
+
- Progress Tracking: Returns progressLog showing conversion, transaction initiation, and confirmation steps
|
|
1165
1819
|
- Use when: User needs to fund storage account before uploads or when balance is insufficient
|
|
1166
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
|
+
|
|
1167
1829
|
PROVIDER MANAGEMENT:
|
|
1168
1830
|
\u2022 getProviders: List storage providers available on the Filecoin network
|
|
1169
1831
|
|
|
@@ -1192,7 +1854,9 @@ PROVIDER MANAGEMENT:
|
|
|
1192
1854
|
|
|
1193
1855
|
5. MONITOR STORAGE METRICS AND PERSISTENCE:
|
|
1194
1856
|
- Check persistence days remaining regularly
|
|
1195
|
-
-
|
|
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
|
|
1196
1860
|
|
|
1197
1861
|
6. VALIDATE FILE PATHS:
|
|
1198
1862
|
- Ensure filePath is absolute path
|
|
@@ -1218,10 +1882,11 @@ FOR DATASET MANAGEMENT:
|
|
|
1218
1882
|
|
|
1219
1883
|
FOR BALANCE MANAGEMENT:
|
|
1220
1884
|
|
|
1221
|
-
1. Check Current State: getBalances
|
|
1222
|
-
2.
|
|
1223
|
-
3.
|
|
1224
|
-
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
|
|
1225
1890
|
|
|
1226
1891
|
\u{1F4A1} STRATEGIC CONSIDERATIONS:
|
|
1227
1892
|
|
|
@@ -1243,6 +1908,8 @@ COST MANAGEMENT:
|
|
|
1243
1908
|
\u2022 Rate allowance: Controls per-epoch spending
|
|
1244
1909
|
\u2022 Lockup allowance: Total committed for long-term storage
|
|
1245
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
|
|
1246
1913
|
|
|
1247
1914
|
\u{1F6A8} ERROR HANDLING:
|
|
1248
1915
|
|
|
@@ -1259,7 +1926,8 @@ DURING UPLOAD:
|
|
|
1259
1926
|
\u2022 Each phase has status updates
|
|
1260
1927
|
|
|
1261
1928
|
COMMON ERRORS:
|
|
1262
|
-
\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
|
|
1263
1931
|
\u2022 "Signer not found": Wallet not connected properly \u2192 check PRIVATE_KEY env var
|
|
1264
1932
|
\u2022 "Transaction failed": User rejected signature or gas issue \u2192 explain and retry
|
|
1265
1933
|
\u2022 "Provider connection failed": Try different provider or retry
|
|
@@ -1294,12 +1962,14 @@ ERROR RESPONSES:
|
|
|
1294
1962
|
\u{1F3AF} AGENT BEHAVIOR GUIDELINES:
|
|
1295
1963
|
|
|
1296
1964
|
1. BE PROACTIVE: Suggest checking balances before uploads
|
|
1297
|
-
2. BE
|
|
1298
|
-
3. BE
|
|
1299
|
-
4. BE
|
|
1300
|
-
5. BE
|
|
1301
|
-
6. BE
|
|
1302
|
-
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
|
|
1303
1973
|
|
|
1304
1974
|
\u{1F510} SECURITY CONSIDERATIONS:
|
|
1305
1975
|
\u2022 Never expose private keys or wallet seeds
|
|
@@ -1352,7 +2022,7 @@ var focStorageResources = {
|
|
|
1352
2022
|
// src/mastra/index.ts
|
|
1353
2023
|
var mcpServer = new MCPServer({
|
|
1354
2024
|
name: "FOC Storage MCP",
|
|
1355
|
-
version: "0.1.
|
|
2025
|
+
version: "0.1.3",
|
|
1356
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.",
|
|
1357
2027
|
tools: focStorageTools,
|
|
1358
2028
|
repository: {
|