@filoz/repair-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +19 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/datasets.d.ts +9 -0
- package/dist/src/commands/datasets.d.ts.map +1 -0
- package/dist/src/commands/datasets.js +59 -0
- package/dist/src/commands/datasets.js.map +1 -0
- package/dist/src/commands/providers.d.ts +9 -0
- package/dist/src/commands/providers.d.ts.map +1 -0
- package/dist/src/commands/providers.js +84 -0
- package/dist/src/commands/providers.js.map +1 -0
- package/dist/src/commands/repair.d.ts +9 -0
- package/dist/src/commands/repair.d.ts.map +1 -0
- package/dist/src/commands/repair.js +170 -0
- package/dist/src/commands/repair.js.map +1 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.d.ts.map +1 -0
- package/dist/src/commands/setup.js +127 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/commands/wallet.d.ts +9 -0
- package/dist/src/commands/wallet.d.ts.map +1 -0
- package/dist/src/commands/wallet.js +150 -0
- package/dist/src/commands/wallet.js.map +1 -0
- package/dist/src/db/get-pieces.d.ts +23 -0
- package/dist/src/db/get-pieces.d.ts.map +1 -0
- package/dist/src/db/get-pieces.js +84 -0
- package/dist/src/db/get-pieces.js.map +1 -0
- package/dist/src/db/get-providers-by-cid.d.ts +10 -0
- package/dist/src/db/get-providers-by-cid.d.ts.map +1 -0
- package/dist/src/db/get-providers-by-cid.js +45 -0
- package/dist/src/db/get-providers-by-cid.js.map +1 -0
- package/dist/src/db/get-repair-dataset.d.ts +9 -0
- package/dist/src/db/get-repair-dataset.d.ts.map +1 -0
- package/dist/src/db/get-repair-dataset.js +15 -0
- package/dist/src/db/get-repair-dataset.js.map +1 -0
- package/dist/src/db/get-repair-provider.d.ts +6 -0
- package/dist/src/db/get-repair-provider.d.ts.map +1 -0
- package/dist/src/db/get-repair-provider.js +28 -0
- package/dist/src/db/get-repair-provider.js.map +1 -0
- package/dist/src/db/get-target-dataset.d.ts +7 -0
- package/dist/src/db/get-target-dataset.d.ts.map +1 -0
- package/dist/src/db/get-target-dataset.js +27 -0
- package/dist/src/db/get-target-dataset.js.map +1 -0
- package/dist/src/db/repair-create.d.ts +7 -0
- package/dist/src/db/repair-create.d.ts.map +1 -0
- package/dist/src/db/repair-create.js +69 -0
- package/dist/src/db/repair-create.js.map +1 -0
- package/dist/src/db/repair-delete.d.ts +11 -0
- package/dist/src/db/repair-delete.d.ts.map +1 -0
- package/dist/src/db/repair-delete.js +22 -0
- package/dist/src/db/repair-delete.js.map +1 -0
- package/dist/src/db/repair-update.d.ts +10 -0
- package/dist/src/db/repair-update.d.ts.map +1 -0
- package/dist/src/db/repair-update.js +13 -0
- package/dist/src/db/repair-update.js.map +1 -0
- package/dist/src/db/sync-pieces-onchain.d.ts +10 -0
- package/dist/src/db/sync-pieces-onchain.d.ts.map +1 -0
- package/dist/src/db/sync-pieces-onchain.js +35 -0
- package/dist/src/db/sync-pieces-onchain.js.map +1 -0
- package/dist/src/db/update-operation.d.ts +11 -0
- package/dist/src/db/update-operation.d.ts.map +1 -0
- package/dist/src/db/update-operation.js +14 -0
- package/dist/src/db/update-operation.js.map +1 -0
- package/dist/src/db/upsert-operations.d.ts +8 -0
- package/dist/src/db/upsert-operations.d.ts.map +1 -0
- package/dist/src/db/upsert-operations.js +13 -0
- package/dist/src/db/upsert-operations.js.map +1 -0
- package/dist/src/error.d.ts +14 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +27 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/indexer-schema.d.ts +549 -0
- package/dist/src/indexer-schema.d.ts.map +1 -0
- package/dist/src/indexer-schema.js +56 -0
- package/dist/src/indexer-schema.js.map +1 -0
- package/dist/src/local-schema.d.ts +456 -0
- package/dist/src/local-schema.d.ts.map +1 -0
- package/dist/src/local-schema.js +63 -0
- package/dist/src/local-schema.js.map +1 -0
- package/dist/src/middleware.d.ts +19 -0
- package/dist/src/middleware.d.ts.map +1 -0
- package/dist/src/middleware.js +28 -0
- package/dist/src/middleware.js.map +1 -0
- package/dist/src/pipeline/create-datasets.d.ts +10 -0
- package/dist/src/pipeline/create-datasets.d.ts.map +1 -0
- package/dist/src/pipeline/create-datasets.js +48 -0
- package/dist/src/pipeline/create-datasets.js.map +1 -0
- package/dist/src/pipeline/pull.d.ts +30 -0
- package/dist/src/pipeline/pull.d.ts.map +1 -0
- package/dist/src/pipeline/pull.js +169 -0
- package/dist/src/pipeline/pull.js.map +1 -0
- package/dist/src/types.d.ts +34 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +11945 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +121 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +135 -0
- package/readme.md +250 -0
- package/src/cli.ts +20 -0
- package/src/commands/datasets.ts +62 -0
- package/src/commands/providers.ts +99 -0
- package/src/commands/repair.ts +177 -0
- package/src/commands/setup.ts +142 -0
- package/src/commands/wallet.ts +159 -0
- package/src/db/get-pieces.ts +189 -0
- package/src/db/get-providers-by-cid.ts +75 -0
- package/src/db/get-repair-dataset.ts +44 -0
- package/src/db/get-repair-provider.ts +47 -0
- package/src/db/get-target-dataset.ts +47 -0
- package/src/db/repair-create.ts +101 -0
- package/src/db/repair-delete.ts +39 -0
- package/src/db/repair-update.ts +20 -0
- package/src/db/sync-pieces-onchain.ts +53 -0
- package/src/db/update-operation.ts +26 -0
- package/src/db/upsert-operations.ts +23 -0
- package/src/error.ts +33 -0
- package/src/indexer-schema.ts +77 -0
- package/src/local-schema.ts +91 -0
- package/src/middleware.ts +34 -0
- package/src/pipeline/create-datasets.ts +70 -0
- package/src/pipeline/pull.ts +255 -0
- package/src/types.ts +41 -0
- package/src/utils.ts +190 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { and, asc, count, eq, inArray, isNull, lte, or, type SQLWrapper, sum } from 'drizzle-orm'
|
|
2
|
+
import { Cli, z } from 'incur'
|
|
3
|
+
import { getBlockNumber } from 'viem/actions'
|
|
4
|
+
import { contextMiddleware, contextSchema } from '../middleware.ts'
|
|
5
|
+
import { globalOptions } from '../utils.ts'
|
|
6
|
+
|
|
7
|
+
/** Format byte count as decimal gigabytes with two fractional digits. */
|
|
8
|
+
function formatBytesAsGb(bytes: bigint): string {
|
|
9
|
+
const scaled = (bytes * 100n) / 1_000_000_000n
|
|
10
|
+
const whole = scaled / 100n
|
|
11
|
+
const fraction = scaled % 100n
|
|
12
|
+
return `${whole}.${fraction.toString().padStart(2, '0')} GB`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const providers = Cli.create('providers', {
|
|
16
|
+
description: 'Provider commands',
|
|
17
|
+
options: globalOptions,
|
|
18
|
+
vars: contextSchema,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
providers.command('list', {
|
|
22
|
+
description: 'List all providers from the indexer',
|
|
23
|
+
options: globalOptions.extend({
|
|
24
|
+
all: z.boolean().optional().default(false).describe('Include all providers'),
|
|
25
|
+
}),
|
|
26
|
+
middleware: [contextMiddleware],
|
|
27
|
+
run: async (c) => {
|
|
28
|
+
try {
|
|
29
|
+
const schema = c.var.indexerDb._.fullSchema
|
|
30
|
+
const filters: (SQLWrapper | undefined)[] = [
|
|
31
|
+
eq(schema.providers.providerActive, true),
|
|
32
|
+
eq(schema.providers.pdpProductActive, true),
|
|
33
|
+
// or(eq(schema.providers.approved, true), eq(schema.providers.endorsed, true)),
|
|
34
|
+
]
|
|
35
|
+
if (!c.options.all) {
|
|
36
|
+
filters.push(or(eq(schema.providers.approved, true), eq(schema.providers.endorsed, true)))
|
|
37
|
+
}
|
|
38
|
+
const blockNumber = await getBlockNumber(c.var.client)
|
|
39
|
+
const rows = await c.var.indexerDb.query.providers.findMany({
|
|
40
|
+
orderBy: [asc(schema.providers.providerId)],
|
|
41
|
+
where: and(...filters),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const providerIds = rows.map((provider) => provider.providerId)
|
|
45
|
+
const statsByProviderId = new Map<bigint, { pieceCount: number; totalSize: bigint }>()
|
|
46
|
+
|
|
47
|
+
if (providerIds.length > 0) {
|
|
48
|
+
const stats = await c.var.indexerDb
|
|
49
|
+
.select({
|
|
50
|
+
providerId: schema.dataSets.providerId,
|
|
51
|
+
pieceCount: count(schema.pieces.pieceId),
|
|
52
|
+
totalSize: sum(schema.pieces.rawSize),
|
|
53
|
+
})
|
|
54
|
+
.from(schema.pieces)
|
|
55
|
+
.innerJoin(schema.dataSets, eq(schema.pieces.dataSetId, schema.dataSets.dataSetId))
|
|
56
|
+
.where(
|
|
57
|
+
and(
|
|
58
|
+
inArray(schema.dataSets.providerId, providerIds),
|
|
59
|
+
eq(schema.dataSets.deleted, false),
|
|
60
|
+
or(isNull(schema.dataSets.pdpEndEpoch), lte(schema.dataSets.pdpEndEpoch, blockNumber)),
|
|
61
|
+
eq(schema.pieces.removed, false)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
.groupBy(schema.dataSets.providerId)
|
|
65
|
+
|
|
66
|
+
for (const stat of stats) {
|
|
67
|
+
statsByProviderId.set(stat.providerId, {
|
|
68
|
+
pieceCount: stat.pieceCount,
|
|
69
|
+
totalSize: stat.totalSize == null ? 0n : BigInt(stat.totalSize),
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const providersFlattened = rows.map((provider) => {
|
|
75
|
+
const stats = statsByProviderId.get(provider.providerId)
|
|
76
|
+
return {
|
|
77
|
+
id: provider.providerId,
|
|
78
|
+
name: provider.name,
|
|
79
|
+
serviceUrl: provider.serviceUrl,
|
|
80
|
+
approved: provider.approved,
|
|
81
|
+
endorsed: provider.endorsed,
|
|
82
|
+
pieceCount: stats?.pieceCount ?? 0,
|
|
83
|
+
totalSize: formatBytesAsGb(stats?.totalSize ?? 0n),
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return c.ok({
|
|
88
|
+
providers: providersFlattened,
|
|
89
|
+
})
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(error)
|
|
92
|
+
return c.error({
|
|
93
|
+
code: 'PROVIDERS_FAILED',
|
|
94
|
+
message: error instanceof Error ? error.message : 'Failed to list providers',
|
|
95
|
+
retryable: true,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { and, desc, eq, inArray } from 'drizzle-orm'
|
|
2
|
+
import { Cli, z } from 'incur'
|
|
3
|
+
import { repairCreate } from '../db/repair-create.ts'
|
|
4
|
+
import { repairDelete } from '../db/repair-delete.ts'
|
|
5
|
+
import { contextMiddleware, contextSchema } from '../middleware.ts'
|
|
6
|
+
import { ensureRepairDataset } from '../pipeline/create-datasets.ts'
|
|
7
|
+
import { runPullPiecesPhase } from '../pipeline/pull.ts'
|
|
8
|
+
import { globalOptions } from '../utils.ts'
|
|
9
|
+
export const repair = Cli.create('repair', {
|
|
10
|
+
description: 'Repair commands',
|
|
11
|
+
vars: contextSchema,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
repair.command('create', {
|
|
15
|
+
description: 'Create a new repair',
|
|
16
|
+
options: globalOptions.extend({
|
|
17
|
+
providerId: z.coerce.bigint().describe('Provider ID to repair'),
|
|
18
|
+
targetProviderId: z.coerce.bigint().describe('Target provider ID for repair'),
|
|
19
|
+
}),
|
|
20
|
+
middleware: [contextMiddleware],
|
|
21
|
+
run: async (c) => {
|
|
22
|
+
try {
|
|
23
|
+
const { providerId, targetProviderId } = c.options
|
|
24
|
+
|
|
25
|
+
const repairId = await repairCreate({
|
|
26
|
+
...c.var,
|
|
27
|
+
repairProviderId: providerId,
|
|
28
|
+
targetProviderId,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return c.ok({
|
|
32
|
+
repairId,
|
|
33
|
+
})
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(error)
|
|
36
|
+
return c.error({
|
|
37
|
+
code: 'REPAIR_FAILED',
|
|
38
|
+
message: error instanceof Error ? error.message : 'Failed to repair the dataset',
|
|
39
|
+
retryable: true,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
repair.command('list', {
|
|
46
|
+
description: 'List all repairs',
|
|
47
|
+
options: globalOptions,
|
|
48
|
+
middleware: [contextMiddleware],
|
|
49
|
+
run: async (c) => {
|
|
50
|
+
try {
|
|
51
|
+
const localSchema = c.var.localDb._.fullSchema
|
|
52
|
+
const repairs = await c.var.localDb.query.repairs.findMany({
|
|
53
|
+
orderBy: [desc(localSchema.repairs.createdAt)],
|
|
54
|
+
with: {
|
|
55
|
+
operations: true,
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const repairFlattened = repairs.map((repair) => {
|
|
60
|
+
const { operations, ...repairWithoutOperations } = repair
|
|
61
|
+
return {
|
|
62
|
+
id: repairWithoutOperations.id,
|
|
63
|
+
status: repairWithoutOperations.status,
|
|
64
|
+
repairProviderId: repairWithoutOperations.repairProviderId,
|
|
65
|
+
targetProviderId: repairWithoutOperations.targetProviderId,
|
|
66
|
+
targetProviderUrl: repairWithoutOperations.targetProviderUrl,
|
|
67
|
+
targetDataSetId: repairWithoutOperations.targetDataSetId,
|
|
68
|
+
blockNumber: repairWithoutOperations.blockNumber,
|
|
69
|
+
operations: operations.length,
|
|
70
|
+
pending: operations.filter((operation) => operation.status === 'pending').length,
|
|
71
|
+
failed: operations.filter((operation) => operation.status === 'failed').length,
|
|
72
|
+
completed: operations.filter((operation) => operation.status === 'completed').length,
|
|
73
|
+
skipped: operations.filter((operation) => operation.status === 'skipped').length,
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return c.ok({
|
|
78
|
+
repairs: repairFlattened,
|
|
79
|
+
})
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(error)
|
|
82
|
+
return c.error({
|
|
83
|
+
code: 'REPAIR_FAILED',
|
|
84
|
+
message: error instanceof Error ? error.message : 'Failed to repair the dataset',
|
|
85
|
+
retryable: true,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
repair.command('delete', {
|
|
92
|
+
description: 'Delete a repair',
|
|
93
|
+
args: z.object({
|
|
94
|
+
repairId: z.coerce.number().describe('Repair ID to delete'),
|
|
95
|
+
}),
|
|
96
|
+
options: globalOptions,
|
|
97
|
+
middleware: [contextMiddleware],
|
|
98
|
+
run: async (c) => {
|
|
99
|
+
try {
|
|
100
|
+
const { deleted, operationsDeleted } = await repairDelete({
|
|
101
|
+
localDb: c.var.localDb,
|
|
102
|
+
repairId: c.args.repairId,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (!deleted) {
|
|
106
|
+
return c.error({
|
|
107
|
+
code: 'REPAIR_NOT_FOUND',
|
|
108
|
+
message: 'Repair not found',
|
|
109
|
+
retryable: false,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return c.ok({
|
|
114
|
+
repairId: c.args.repairId,
|
|
115
|
+
operationsDeleted,
|
|
116
|
+
})
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(error)
|
|
119
|
+
return c.error({
|
|
120
|
+
code: 'REPAIR_FAILED',
|
|
121
|
+
message: error instanceof Error ? error.message : 'Failed to delete the repair',
|
|
122
|
+
retryable: true,
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
repair.command('run', {
|
|
129
|
+
description: 'Run a repair',
|
|
130
|
+
args: z.object({
|
|
131
|
+
repairId: z.coerce.number().describe('Repair ID to run'),
|
|
132
|
+
}),
|
|
133
|
+
options: globalOptions.extend({
|
|
134
|
+
concurrency: z.coerce.number().default(4).describe('Concurrency level'),
|
|
135
|
+
batchSize: z.coerce.number().default(40).describe('Max add_piece operations per pull batch'),
|
|
136
|
+
reset: z.boolean().default(false).describe('Reset the repair'),
|
|
137
|
+
}),
|
|
138
|
+
middleware: [contextMiddleware],
|
|
139
|
+
run: async (c) => {
|
|
140
|
+
try {
|
|
141
|
+
const schema = c.var.localDb._.fullSchema
|
|
142
|
+
const repair = await c.var.localDb.query.repairs.findFirst({
|
|
143
|
+
where: and(eq(schema.repairs.id, c.args.repairId), inArray(schema.repairs.status, ['pending'])),
|
|
144
|
+
})
|
|
145
|
+
if (!repair) {
|
|
146
|
+
return c.error({
|
|
147
|
+
code: 'REPAIR_NOT_FOUND',
|
|
148
|
+
message: 'Repair not found, it may have already been run or completed',
|
|
149
|
+
retryable: false,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await ensureRepairDataset({
|
|
154
|
+
...c.var,
|
|
155
|
+
repair,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
await runPullPiecesPhase({
|
|
159
|
+
...c.var,
|
|
160
|
+
repair,
|
|
161
|
+
concurrency: c.options.concurrency,
|
|
162
|
+
batchSize: c.options.batchSize,
|
|
163
|
+
reset: c.options.reset,
|
|
164
|
+
})
|
|
165
|
+
return c.ok({
|
|
166
|
+
repairId: repair.id,
|
|
167
|
+
})
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(error)
|
|
170
|
+
return c.error({
|
|
171
|
+
code: 'REPAIR_FAILED',
|
|
172
|
+
message: error instanceof Error ? error.message : 'Failed to repair the dataset',
|
|
173
|
+
retryable: true,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
})
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import * as p from '@clack/prompts'
|
|
2
|
+
import { Cli, z } from 'incur'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import type { Hash } from 'viem'
|
|
5
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
6
|
+
import { config, createLocalDatabase, globalOptions, migrateLocalDatabase } from '../utils.ts'
|
|
7
|
+
|
|
8
|
+
function validatePostgresUrl(value: string) {
|
|
9
|
+
let url: URL
|
|
10
|
+
try {
|
|
11
|
+
url = new URL(value)
|
|
12
|
+
} catch {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (url.protocol !== 'postgresql:' && url.protocol !== 'postgres:') {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Boolean(url.hostname && url.username && url.password && url.pathname.length > 1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const setup = Cli.create('setup', {
|
|
24
|
+
description: 'Setup the CLI',
|
|
25
|
+
options: globalOptions.extend({
|
|
26
|
+
privateKey: z.string().optional().describe('Private key to use'),
|
|
27
|
+
}),
|
|
28
|
+
run: async (c) => {
|
|
29
|
+
try {
|
|
30
|
+
// Private key
|
|
31
|
+
const pk = await p.text({
|
|
32
|
+
message: 'Enter your private key',
|
|
33
|
+
validate(value) {
|
|
34
|
+
if (!value || !/^0x[a-fA-F0-9]{64}$/.test(value)) {
|
|
35
|
+
return `Invalid private key!`
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
initialValue: config.get('privateKey'),
|
|
39
|
+
withGuide: false,
|
|
40
|
+
})
|
|
41
|
+
if (p.isCancel(pk)) {
|
|
42
|
+
return c.error({
|
|
43
|
+
code: 'SETUP_CANCELLED',
|
|
44
|
+
message: 'Setup cancelled',
|
|
45
|
+
retryable: false,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Indexer URL
|
|
50
|
+
const indexerMainnetUrl = await p.text({
|
|
51
|
+
message: 'Enter your Mainnet Indexer Postgres URL',
|
|
52
|
+
validate(value) {
|
|
53
|
+
if (!value || !validatePostgresUrl(value)) {
|
|
54
|
+
return `Invalid postgres URL!`
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
initialValue: config.get('indexerMainnetUrl'),
|
|
58
|
+
withGuide: false,
|
|
59
|
+
})
|
|
60
|
+
if (p.isCancel(indexerMainnetUrl)) {
|
|
61
|
+
return c.error({
|
|
62
|
+
code: 'SETUP_CANCELLED',
|
|
63
|
+
message: 'Setup cancelled',
|
|
64
|
+
retryable: false,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
const indexerCalibrationUrl = await p.text({
|
|
68
|
+
message: 'Enter your Calibration Indexer Postgres URL',
|
|
69
|
+
validate(value) {
|
|
70
|
+
if (!value || !validatePostgresUrl(value)) {
|
|
71
|
+
return `Invalid postgres URL!`
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
initialValue: config.get('indexerCalibrationUrl'),
|
|
75
|
+
withGuide: false,
|
|
76
|
+
})
|
|
77
|
+
if (p.isCancel(indexerCalibrationUrl)) {
|
|
78
|
+
return c.error({
|
|
79
|
+
code: 'SETUP_CANCELLED',
|
|
80
|
+
message: 'Setup cancelled',
|
|
81
|
+
retryable: false,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Chain
|
|
86
|
+
const chainId = await p.select({
|
|
87
|
+
message: 'Select your chain',
|
|
88
|
+
options: [
|
|
89
|
+
{ value: 314, label: 'Mainnet' },
|
|
90
|
+
{ value: 314159, label: 'Calibration' },
|
|
91
|
+
],
|
|
92
|
+
withGuide: false,
|
|
93
|
+
initialValue: config.get('chainId'),
|
|
94
|
+
})
|
|
95
|
+
if (p.isCancel(chainId)) {
|
|
96
|
+
return c.error({
|
|
97
|
+
code: 'SETUP_CANCELLED',
|
|
98
|
+
message: 'Setup cancelled',
|
|
99
|
+
retryable: false,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// DB path
|
|
104
|
+
const dbPath = await p.text({
|
|
105
|
+
message: 'Enter your DB path',
|
|
106
|
+
initialValue: config.get('dbPath') || path.join(path.dirname(config.path), 'sqlite.db'),
|
|
107
|
+
withGuide: false,
|
|
108
|
+
})
|
|
109
|
+
if (p.isCancel(dbPath)) {
|
|
110
|
+
return c.error({
|
|
111
|
+
code: 'SETUP_CANCELLED',
|
|
112
|
+
message: 'Setup cancelled',
|
|
113
|
+
retryable: false,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Set config
|
|
118
|
+
config.set('privateKey', pk)
|
|
119
|
+
config.set('indexerMainnetUrl', indexerMainnetUrl)
|
|
120
|
+
config.set('indexerCalibrationUrl', indexerCalibrationUrl)
|
|
121
|
+
config.set('chainId', chainId)
|
|
122
|
+
config.set('dbPath', dbPath)
|
|
123
|
+
|
|
124
|
+
// setup database
|
|
125
|
+
const db = await createLocalDatabase(dbPath)
|
|
126
|
+
await migrateLocalDatabase(db)
|
|
127
|
+
|
|
128
|
+
const account = privateKeyToAccount(pk as Hash)
|
|
129
|
+
|
|
130
|
+
return c.ok({
|
|
131
|
+
address: account.address,
|
|
132
|
+
})
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(error)
|
|
135
|
+
return c.error({
|
|
136
|
+
code: 'SETUP_FAILED',
|
|
137
|
+
message: error instanceof Error ? error.message : 'Failed to setup the CLI',
|
|
138
|
+
retryable: true,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noConsole: cli */
|
|
2
|
+
import { calibration } from '@filoz/synapse-core/chains'
|
|
3
|
+
import * as ERC20 from '@filoz/synapse-core/erc20'
|
|
4
|
+
import * as Pay from '@filoz/synapse-core/pay'
|
|
5
|
+
import { claimTokens, formatBalance, formatFraction, parseUnits } from '@filoz/synapse-core/utils'
|
|
6
|
+
import { Cli, z } from 'incur'
|
|
7
|
+
import { getBalance, waitForTransactionReceipt } from 'viem/actions'
|
|
8
|
+
import { contextMiddleware, contextSchema } from '../middleware.ts'
|
|
9
|
+
import { globalOptions, hashLink } from '../utils.ts'
|
|
10
|
+
|
|
11
|
+
export const wallet = Cli.create('wallet', {
|
|
12
|
+
description: 'Wallet commands',
|
|
13
|
+
vars: contextSchema,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
wallet.command('fund', {
|
|
17
|
+
description: 'Fund a calibration wallet from a faucet',
|
|
18
|
+
options: globalOptions,
|
|
19
|
+
middleware: [contextMiddleware],
|
|
20
|
+
async *run(c) {
|
|
21
|
+
const { client, chain } = c.var
|
|
22
|
+
|
|
23
|
+
if (chain.id !== calibration.id) {
|
|
24
|
+
return c.error({
|
|
25
|
+
code: 'INVALID_CHAIN',
|
|
26
|
+
message: `Wallet fund is only available on Filecoin Calibration (chain ID ${calibration.id})`,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
yield 'Funding wallet...'
|
|
31
|
+
try {
|
|
32
|
+
const hashes = await claimTokens({ address: client.account.address })
|
|
33
|
+
|
|
34
|
+
yield `Waiting for tx ${hashLink(hashes[0].tx_hash, chain)} to be mined...`
|
|
35
|
+
await waitForTransactionReceipt(client, {
|
|
36
|
+
hash: hashes[0].tx_hash,
|
|
37
|
+
})
|
|
38
|
+
const balance = await getBalance(client, {
|
|
39
|
+
address: client.account.address,
|
|
40
|
+
})
|
|
41
|
+
yield {
|
|
42
|
+
address: client.account.address,
|
|
43
|
+
balance: formatBalance({ value: balance }),
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (c.options.debug) {
|
|
47
|
+
console.error(error)
|
|
48
|
+
}
|
|
49
|
+
return c.error({
|
|
50
|
+
code: 'FAILED_TO_FUND_WALLET',
|
|
51
|
+
message: 'Failed to fund wallet',
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
wallet.command('balance', {
|
|
58
|
+
description: 'Get wallet and pay account summary',
|
|
59
|
+
options: globalOptions,
|
|
60
|
+
middleware: [contextMiddleware],
|
|
61
|
+
async run(c) {
|
|
62
|
+
const { client } = c.var
|
|
63
|
+
const balanceFIL = await getBalance(client, {
|
|
64
|
+
address: client.account.address,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const balanceUSDFC = await ERC20.balance(client, {
|
|
68
|
+
address: client.account.address,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const summary = await Pay.getAccountSummary(client, {
|
|
72
|
+
address: client.account.address,
|
|
73
|
+
})
|
|
74
|
+
return {
|
|
75
|
+
address: client.account.address,
|
|
76
|
+
fil: formatBalance({ value: balanceFIL }),
|
|
77
|
+
usdfc: formatBalance({ value: balanceUSDFC.value }),
|
|
78
|
+
pay: {
|
|
79
|
+
funds: formatBalance({ value: summary.funds }),
|
|
80
|
+
availableFunds: formatBalance({ value: summary.availableFunds }),
|
|
81
|
+
debt: formatBalance({ value: summary.debt }),
|
|
82
|
+
lockupRatePerEpoch: formatFraction({ value: summary.lockupRatePerEpoch }),
|
|
83
|
+
lockupRatePerMonth: formatBalance({ value: summary.lockupRatePerMonth }),
|
|
84
|
+
totalLockup: formatBalance({ value: summary.totalLockup }),
|
|
85
|
+
totalFixedLockup: formatBalance({ value: summary.totalFixedLockup }),
|
|
86
|
+
totalRateBasedLockup: formatBalance({ value: summary.totalRateBasedLockup }),
|
|
87
|
+
runwayInEpochs: summary.runwayInEpochs,
|
|
88
|
+
grossCoverageInEpochs: summary.grossCoverageInEpochs,
|
|
89
|
+
epoch: summary.epoch,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
wallet.command('deposit', {
|
|
96
|
+
description: 'Deposit wallet funds to a pay account',
|
|
97
|
+
args: z.object({
|
|
98
|
+
amount: z.coerce.number().gt(0).describe('Amount of USDFC to deposit'),
|
|
99
|
+
}),
|
|
100
|
+
options: globalOptions,
|
|
101
|
+
middleware: [contextMiddleware],
|
|
102
|
+
async *run(c) {
|
|
103
|
+
const { client, chain } = c.var
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
yield `Depositing ${c.args.amount} tokens to wallet...`
|
|
107
|
+
const hash = await Pay.depositAndApprove(client, {
|
|
108
|
+
amount: parseUnits(c.args.amount),
|
|
109
|
+
})
|
|
110
|
+
yield `Waiting for tx ${hashLink(hash, chain)} to be mined...`
|
|
111
|
+
await waitForTransactionReceipt(client, {
|
|
112
|
+
hash,
|
|
113
|
+
})
|
|
114
|
+
yield `Deposit successful`
|
|
115
|
+
return
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (c.options.debug) {
|
|
118
|
+
console.error(error)
|
|
119
|
+
}
|
|
120
|
+
return c.error({
|
|
121
|
+
code: 'FAILED_TO_DEPOSIT',
|
|
122
|
+
message: (error as Error).message,
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
wallet.command('withdraw', {
|
|
129
|
+
description: 'Withdraw wallet funds from a pay account',
|
|
130
|
+
args: z.object({
|
|
131
|
+
amount: z.coerce.number().gt(0).describe('Amount of USDFC to withdraw'),
|
|
132
|
+
}),
|
|
133
|
+
options: globalOptions,
|
|
134
|
+
middleware: [contextMiddleware],
|
|
135
|
+
async *run(c) {
|
|
136
|
+
const { client, chain } = c.var
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
yield `Withdrawing ${c.args.amount} USDFC from pay account...`
|
|
140
|
+
const hash = await Pay.withdraw(client, {
|
|
141
|
+
amount: parseUnits(c.args.amount),
|
|
142
|
+
})
|
|
143
|
+
yield `Waiting for tx ${hashLink(hash, chain)} to be mined...`
|
|
144
|
+
await waitForTransactionReceipt(client, {
|
|
145
|
+
hash,
|
|
146
|
+
})
|
|
147
|
+
yield `Withdrawal successful`
|
|
148
|
+
return
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (c.options.debug) {
|
|
151
|
+
console.error(error)
|
|
152
|
+
}
|
|
153
|
+
return c.error({
|
|
154
|
+
code: 'FAILED_TO_WITHDRAW',
|
|
155
|
+
message: (error as Error).message,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
})
|