@helium/blockchain-api 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/package.json +31 -31
- package/types/README.md +24 -2
- package/types/generated/index.d.mts +2481 -0
- package/types/generated/index.d.ts +2481 -0
- package/types/generated/index.js +723 -0
- package/types/generated/index.mjs +614 -0
- package/types/index.ts +11 -8
- package/src/server/api/errors.ts +0 -152
- package/src/server/api/index.ts +0 -40
- package/src/server/api/procedures.ts +0 -144
- package/src/server/api/routers/fiat/router.ts +0 -709
- package/src/server/api/routers/fiat/schemas.ts +0 -169
- package/src/server/api/routers/health/router.ts +0 -41
- package/src/server/api/routers/hotspots/procedures/claimRewards.ts +0 -258
- package/src/server/api/routers/hotspots/procedures/createSplit.ts +0 -253
- package/src/server/api/routers/hotspots/procedures/deleteSplit.ts +0 -156
- package/src/server/api/routers/hotspots/procedures/getHotspots.ts +0 -31
- package/src/server/api/routers/hotspots/procedures/getPendingRewards.ts +0 -44
- package/src/server/api/routers/hotspots/procedures/getSplit.ts +0 -88
- package/src/server/api/routers/hotspots/procedures/transferHotspot.ts +0 -204
- package/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts +0 -201
- package/src/server/api/routers/hotspots/router.ts +0 -30
- package/src/server/api/routers/hotspots/schemas.ts +0 -182
- package/src/server/api/routers/swap/procedures/getInstructions.ts +0 -152
- package/src/server/api/routers/swap/procedures/getQuote.ts +0 -53
- package/src/server/api/routers/swap/procedures/getTokens.ts +0 -88
- package/src/server/api/routers/swap/router.ts +0 -15
- package/src/server/api/routers/swap/schemas.ts +0 -96
- package/src/server/api/routers/tokens/procedures/createHntAccount.ts +0 -87
- package/src/server/api/routers/tokens/procedures/getBalances.ts +0 -27
- package/src/server/api/routers/tokens/procedures/transfer.ts +0 -159
- package/src/server/api/routers/tokens/router.ts +0 -15
- package/src/server/api/routers/tokens/schemas.ts +0 -80
- package/src/server/api/routers/transactions/procedures/get.ts +0 -46
- package/src/server/api/routers/transactions/procedures/getByPayer.ts +0 -111
- package/src/server/api/routers/transactions/procedures/getByPayerAndTag.ts +0 -119
- package/src/server/api/routers/transactions/procedures/resubmit.ts +0 -68
- package/src/server/api/routers/transactions/procedures/submit.ts +0 -216
- package/src/server/api/routers/transactions/router.ts +0 -21
- package/src/server/api/routers/transactions/schemas.ts +0 -119
- package/src/server/api/routers/webhooks/router.ts +0 -75
- package/src/server/api/routers/welcomePacks/procedures/claim.ts +0 -157
- package/src/server/api/routers/welcomePacks/procedures/create.ts +0 -247
- package/src/server/api/routers/welcomePacks/procedures/deletePack.ts +0 -118
- package/src/server/api/routers/welcomePacks/procedures/get.ts +0 -36
- package/src/server/api/routers/welcomePacks/procedures/getByAddress.ts +0 -26
- package/src/server/api/routers/welcomePacks/procedures/invite.ts +0 -44
- package/src/server/api/routers/welcomePacks/procedures/list.ts +0 -27
- package/src/server/api/routers/welcomePacks/router.ts +0 -27
- package/src/server/api/routers/welcomePacks/schemas.ts +0 -135
- package/src/server/api/schemas.ts +0 -281
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// Input Schemas
|
|
5
|
-
// ============================================================================
|
|
6
|
-
|
|
7
|
-
export const GetBalancesInputSchema = z.object({
|
|
8
|
-
walletAddress: z.string().min(32),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export const TransferInputSchema = z.object({
|
|
12
|
-
walletAddress: z.string().min(32),
|
|
13
|
-
mint: z.string().nullable().optional(),
|
|
14
|
-
destination: z.string().min(32),
|
|
15
|
-
amount: z.string(),
|
|
16
|
-
decimals: z.number().optional(),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const CreateHntAccountInputSchema = z.object({
|
|
20
|
-
walletAddress: z.string().min(32),
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// ============================================================================
|
|
24
|
-
// Output Schemas
|
|
25
|
-
// ============================================================================
|
|
26
|
-
|
|
27
|
-
export const TokenAccountSchema = z.object({
|
|
28
|
-
mint: z.string(),
|
|
29
|
-
address: z.string(),
|
|
30
|
-
balance: z.string(),
|
|
31
|
-
decimals: z.number(),
|
|
32
|
-
uiAmount: z.number(),
|
|
33
|
-
symbol: z.string().optional(),
|
|
34
|
-
name: z.string().optional(),
|
|
35
|
-
logoURI: z.string().optional(),
|
|
36
|
-
priceUsd: z.number().optional(),
|
|
37
|
-
balanceUsd: z.number().optional(),
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
export const TokenBalanceDataSchema = z.object({
|
|
41
|
-
totalBalanceUsd: z.number(),
|
|
42
|
-
solBalance: z.number(),
|
|
43
|
-
solBalanceUsd: z.number(),
|
|
44
|
-
tokens: z.array(TokenAccountSchema),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
export const TransactionMetadataSchema = z
|
|
48
|
-
.object({
|
|
49
|
-
type: z.string(),
|
|
50
|
-
description: z.string(),
|
|
51
|
-
})
|
|
52
|
-
.catchall(z.unknown());
|
|
53
|
-
|
|
54
|
-
export const TransactionItemSchema = z.object({
|
|
55
|
-
serializedTransaction: z.string(),
|
|
56
|
-
metadata: TransactionMetadataSchema.optional(),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
export const TransactionDataSchema = z.object({
|
|
60
|
-
transactions: z.array(TransactionItemSchema),
|
|
61
|
-
parallel: z.boolean(),
|
|
62
|
-
tag: z.string().optional(),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
export const TransferOutputSchema = z.object({
|
|
66
|
-
transactionData: TransactionDataSchema,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
export const CreateHntAccountOutputSchema = z.object({
|
|
70
|
-
transactionData: TransactionDataSchema,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// Type Exports
|
|
75
|
-
// ============================================================================
|
|
76
|
-
|
|
77
|
-
export type GetBalancesInput = z.infer<typeof GetBalancesInputSchema>;
|
|
78
|
-
export type TransferInput = z.infer<typeof TransferInputSchema>;
|
|
79
|
-
export type CreateHntAccountInput = z.infer<typeof CreateHntAccountInputSchema>;
|
|
80
|
-
export type TokenBalanceData = z.infer<typeof TokenBalanceDataSchema>;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetInputSchema, BatchStatusOutputSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import PendingTransaction from "@/lib/models/pending-transaction";
|
|
5
|
-
import TransactionBatch from "@/lib/models/transaction-batch";
|
|
6
|
-
import { checkAndUpdateBatchStatus } from "@/lib/utils/transaction-status-checker";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get transaction batch status by ID.
|
|
10
|
-
*/
|
|
11
|
-
export const get = publicProcedure
|
|
12
|
-
.route({ method: "GET", path: "/transactions/{id}" })
|
|
13
|
-
.input(GetInputSchema)
|
|
14
|
-
.output(BatchStatusOutputSchema)
|
|
15
|
-
.errors({
|
|
16
|
-
NOT_FOUND: { message: "Batch not found" },
|
|
17
|
-
})
|
|
18
|
-
.handler(async ({ input, errors }) => {
|
|
19
|
-
const { id, commitment } = input;
|
|
20
|
-
|
|
21
|
-
// First check if this is a batch ID
|
|
22
|
-
const batch = await TransactionBatch.findByPk(id, {
|
|
23
|
-
include: [
|
|
24
|
-
{
|
|
25
|
-
model: PendingTransaction,
|
|
26
|
-
as: "transactions",
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
if (!batch) {
|
|
32
|
-
throw errors.NOT_FOUND({ message: "Batch not found" });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const result = await checkAndUpdateBatchStatus(batch, commitment);
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
batchId: batch.id,
|
|
39
|
-
status: result.batchStatus,
|
|
40
|
-
submissionType: batch.submissionType,
|
|
41
|
-
parallel: batch.parallel,
|
|
42
|
-
transactions: result.transactionStatuses,
|
|
43
|
-
jitoBundleId: batch.jitoBundleId,
|
|
44
|
-
jitoBundleStatus: result.jitoBundleStatus,
|
|
45
|
-
};
|
|
46
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetByPayerInputSchema, PayerBatchesOutputSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import PendingTransaction from "@/lib/models/pending-transaction";
|
|
5
|
-
import TransactionBatch, { BatchStatus } from "@/lib/models/transaction-batch";
|
|
6
|
-
import { connectToDb } from "@/lib/utils/db";
|
|
7
|
-
import { Op } from "sequelize";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Get transaction batches by payer address.
|
|
11
|
-
*/
|
|
12
|
-
export const getByPayer = publicProcedure
|
|
13
|
-
.route({ method: "GET", path: "/transactions/payer/{payer}" })
|
|
14
|
-
.input(GetByPayerInputSchema)
|
|
15
|
-
.output(PayerBatchesOutputSchema)
|
|
16
|
-
.errors({
|
|
17
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
18
|
-
})
|
|
19
|
-
.handler(async ({ input, errors }) => {
|
|
20
|
-
const { payer, page, limit, status } = input;
|
|
21
|
-
|
|
22
|
-
await connectToDb();
|
|
23
|
-
|
|
24
|
-
if (!payer) {
|
|
25
|
-
throw errors.BAD_REQUEST({ message: "Payer is required" });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Parse status filter
|
|
29
|
-
let statusFilter: BatchStatus[] = ["pending"];
|
|
30
|
-
|
|
31
|
-
if (status) {
|
|
32
|
-
const requestedStatuses = status
|
|
33
|
-
.split(",")
|
|
34
|
-
.map((s) => s.trim()) as BatchStatus[];
|
|
35
|
-
const validStatuses: BatchStatus[] = [
|
|
36
|
-
"pending",
|
|
37
|
-
"confirmed",
|
|
38
|
-
"failed",
|
|
39
|
-
"expired",
|
|
40
|
-
"partial",
|
|
41
|
-
];
|
|
42
|
-
statusFilter = requestedStatuses.filter((s) => validStatuses.includes(s));
|
|
43
|
-
|
|
44
|
-
if (statusFilter.length === 0) {
|
|
45
|
-
throw errors.BAD_REQUEST({
|
|
46
|
-
message:
|
|
47
|
-
"Invalid status filter. Valid values: pending, confirmed, failed, expired, partial",
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const offset = (page - 1) * limit;
|
|
53
|
-
|
|
54
|
-
// Build where clause
|
|
55
|
-
const whereClause = {
|
|
56
|
-
payer,
|
|
57
|
-
status: {
|
|
58
|
-
[Op.in]: statusFilter,
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Get total count for pagination
|
|
63
|
-
const total = await TransactionBatch.count({
|
|
64
|
-
where: whereClause,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Find paginated transaction batches for the payer
|
|
68
|
-
const batches = await TransactionBatch.findAll({
|
|
69
|
-
where: whereClause,
|
|
70
|
-
include: [
|
|
71
|
-
{
|
|
72
|
-
model: PendingTransaction,
|
|
73
|
-
as: "transactions",
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
order: [["createdAt", "DESC"]],
|
|
77
|
-
limit,
|
|
78
|
-
offset,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const batchSummaries = batches.map((batch) => ({
|
|
82
|
-
batchId: batch.id,
|
|
83
|
-
tag: batch.tag || undefined,
|
|
84
|
-
status: batch.status,
|
|
85
|
-
submissionType: batch.submissionType,
|
|
86
|
-
parallel: batch.parallel,
|
|
87
|
-
createdAt: batch.createdAt.toISOString(),
|
|
88
|
-
updatedAt: batch.updatedAt.toISOString(),
|
|
89
|
-
transactions:
|
|
90
|
-
(
|
|
91
|
-
batch as unknown as { transactions?: { metadata?: unknown }[] }
|
|
92
|
-
).transactions?.map((tx) => ({
|
|
93
|
-
metadata: tx.metadata as
|
|
94
|
-
| { type: string; description: string }
|
|
95
|
-
| undefined,
|
|
96
|
-
})) || [],
|
|
97
|
-
}));
|
|
98
|
-
|
|
99
|
-
const totalPages = Math.ceil(total / limit);
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
payer,
|
|
103
|
-
batches: batchSummaries,
|
|
104
|
-
pagination: {
|
|
105
|
-
page,
|
|
106
|
-
limit,
|
|
107
|
-
total,
|
|
108
|
-
totalPages,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import {
|
|
3
|
-
GetByPayerAndTagInputSchema,
|
|
4
|
-
PayerBatchesOutputSchema,
|
|
5
|
-
} from "../schemas";
|
|
6
|
-
import { ORPCError } from "@orpc/server";
|
|
7
|
-
import PendingTransaction from "@/lib/models/pending-transaction";
|
|
8
|
-
import TransactionBatch, { BatchStatus } from "@/lib/models/transaction-batch";
|
|
9
|
-
import { connectToDb } from "@/lib/utils/db";
|
|
10
|
-
import { Op } from "sequelize";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get transaction batches by payer address and tag.
|
|
14
|
-
*/
|
|
15
|
-
export const getByPayerAndTag = publicProcedure
|
|
16
|
-
.route({ method: "GET", path: "/transactions/payer/{payer}/tag/{tag}" })
|
|
17
|
-
.input(GetByPayerAndTagInputSchema)
|
|
18
|
-
.output(PayerBatchesOutputSchema)
|
|
19
|
-
.errors({
|
|
20
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
21
|
-
})
|
|
22
|
-
.handler(async ({ input, errors }) => {
|
|
23
|
-
const { payer, tag, page, limit, status } = input;
|
|
24
|
-
|
|
25
|
-
await connectToDb();
|
|
26
|
-
|
|
27
|
-
if (!payer) {
|
|
28
|
-
throw errors.BAD_REQUEST({ message: "Payer is required" });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!tag) {
|
|
32
|
-
throw errors.BAD_REQUEST({ message: "Tag is required" });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Parse status filter
|
|
36
|
-
let statusFilter: BatchStatus[] = ["pending"];
|
|
37
|
-
|
|
38
|
-
if (status) {
|
|
39
|
-
const requestedStatuses = status
|
|
40
|
-
.split(",")
|
|
41
|
-
.map((s) => s.trim()) as BatchStatus[];
|
|
42
|
-
const validStatuses: BatchStatus[] = [
|
|
43
|
-
"pending",
|
|
44
|
-
"confirmed",
|
|
45
|
-
"failed",
|
|
46
|
-
"expired",
|
|
47
|
-
"partial",
|
|
48
|
-
];
|
|
49
|
-
statusFilter = requestedStatuses.filter((s) => validStatuses.includes(s));
|
|
50
|
-
|
|
51
|
-
if (statusFilter.length === 0) {
|
|
52
|
-
throw errors.BAD_REQUEST({
|
|
53
|
-
message:
|
|
54
|
-
"Invalid status filter. Valid values: pending, confirmed, failed, expired, partial",
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const offset = (page - 1) * limit;
|
|
60
|
-
|
|
61
|
-
// Build where clause
|
|
62
|
-
const whereClause = {
|
|
63
|
-
payer,
|
|
64
|
-
tag,
|
|
65
|
-
status: {
|
|
66
|
-
[Op.in]: statusFilter,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Get total count for pagination
|
|
71
|
-
const total = await TransactionBatch.count({
|
|
72
|
-
where: whereClause,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Find paginated transaction batches for the payer and tag
|
|
76
|
-
const batches = await TransactionBatch.findAll({
|
|
77
|
-
where: whereClause,
|
|
78
|
-
include: [
|
|
79
|
-
{
|
|
80
|
-
model: PendingTransaction,
|
|
81
|
-
as: "transactions",
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
order: [["createdAt", "DESC"]],
|
|
85
|
-
limit,
|
|
86
|
-
offset,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const batchSummaries = batches.map((batch) => ({
|
|
90
|
-
batchId: batch.id,
|
|
91
|
-
tag: batch.tag || undefined,
|
|
92
|
-
status: batch.status,
|
|
93
|
-
submissionType: batch.submissionType,
|
|
94
|
-
parallel: batch.parallel,
|
|
95
|
-
createdAt: batch.createdAt.toISOString(),
|
|
96
|
-
updatedAt: batch.updatedAt.toISOString(),
|
|
97
|
-
transactions:
|
|
98
|
-
(
|
|
99
|
-
batch as unknown as { transactions?: { metadata?: unknown }[] }
|
|
100
|
-
).transactions?.map((tx) => ({
|
|
101
|
-
metadata: tx.metadata as
|
|
102
|
-
| { type: string; description: string }
|
|
103
|
-
| undefined,
|
|
104
|
-
})) || [],
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
const totalPages = Math.ceil(total / limit);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
payer,
|
|
111
|
-
batches: batchSummaries,
|
|
112
|
-
pagination: {
|
|
113
|
-
page,
|
|
114
|
-
limit,
|
|
115
|
-
total,
|
|
116
|
-
totalPages,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { ResubmitInputSchema, ResubmitOutputSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import TransactionBatch from "@/lib/models/transaction-batch";
|
|
5
|
-
import PendingTransaction from "@/lib/models/pending-transaction";
|
|
6
|
-
import { resubmitTransactionBatch } from "@/lib/utils/transaction-resubmission";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Resubmit a batch of pending transactions that may have failed.
|
|
10
|
-
*/
|
|
11
|
-
export const resubmit = publicProcedure
|
|
12
|
-
.route({ method: "POST", path: "/transactions/{id}/resubmit" })
|
|
13
|
-
.input(ResubmitInputSchema)
|
|
14
|
-
.output(ResubmitOutputSchema)
|
|
15
|
-
.errors({
|
|
16
|
-
NOT_FOUND: { message: "Batch not found" },
|
|
17
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
18
|
-
})
|
|
19
|
-
.handler(async ({ input, errors }) => {
|
|
20
|
-
const { id } = input;
|
|
21
|
-
|
|
22
|
-
// Find the batch
|
|
23
|
-
const batch = await TransactionBatch.findByPk(id, {
|
|
24
|
-
include: [
|
|
25
|
-
{
|
|
26
|
-
model: PendingTransaction,
|
|
27
|
-
as: "transactions",
|
|
28
|
-
where: {
|
|
29
|
-
status: "pending",
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!batch) {
|
|
36
|
-
throw errors.NOT_FOUND({ message: "Batch not found" });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (batch.status !== "pending") {
|
|
40
|
-
throw errors.BAD_REQUEST({ message: "Batch is not in pending status" });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const pendingTransactions =
|
|
44
|
-
(batch as unknown as { transactions?: PendingTransaction[] })
|
|
45
|
-
.transactions || [];
|
|
46
|
-
if (pendingTransactions.length === 0) {
|
|
47
|
-
throw errors.BAD_REQUEST({
|
|
48
|
-
message: "No pending transactions to resubmit",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Attempt resubmission
|
|
53
|
-
const result = await resubmitTransactionBatch(batch, pendingTransactions);
|
|
54
|
-
|
|
55
|
-
if (result.success) {
|
|
56
|
-
return {
|
|
57
|
-
success: true,
|
|
58
|
-
message: "Transactions resubmitted successfully",
|
|
59
|
-
...(result.newSignatures && { newSignatures: result.newSignatures }),
|
|
60
|
-
};
|
|
61
|
-
} else {
|
|
62
|
-
return {
|
|
63
|
-
success: false,
|
|
64
|
-
message: "Failed to resubmit transactions",
|
|
65
|
-
error: result.error,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
});
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { SubmitInputSchema, SubmitOutputSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import { sequelize } from "@/lib/db";
|
|
5
|
-
import { env } from "@/lib/env";
|
|
6
|
-
import PendingTransaction from "@/lib/models/pending-transaction";
|
|
7
|
-
import TransactionBatch from "@/lib/models/transaction-batch";
|
|
8
|
-
import {
|
|
9
|
-
getCluster,
|
|
10
|
-
submitTransactionBatch,
|
|
11
|
-
} from "@/lib/utils/transaction-submission";
|
|
12
|
-
import { Connection, VersionedTransaction } from "@solana/web3.js";
|
|
13
|
-
|
|
14
|
-
function getExplorerUrl(transaction: VersionedTransaction) {
|
|
15
|
-
const message = Buffer.from(transaction.message.serialize()).toString(
|
|
16
|
-
"base64",
|
|
17
|
-
);
|
|
18
|
-
return `https://explorer.solana.com/tx/inspector?cluster=${
|
|
19
|
-
process.env.NEXT_PUBLIC_SOLANA_CLUSTER
|
|
20
|
-
}&message=${encodeURIComponent(message)}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Submit a batch of transactions for processing.
|
|
25
|
-
*/
|
|
26
|
-
export const submit = publicProcedure
|
|
27
|
-
.route({ method: "POST", path: "/transactions" })
|
|
28
|
-
.input(SubmitInputSchema)
|
|
29
|
-
.output(SubmitOutputSchema)
|
|
30
|
-
.errors({
|
|
31
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
32
|
-
SIMULATION_FAILED: { message: "Transaction simulation failed" },
|
|
33
|
-
CONFLICT: { message: "Duplicate transaction" },
|
|
34
|
-
})
|
|
35
|
-
.handler(async ({ input, errors }) => {
|
|
36
|
-
const { transactions, parallel, tag } = input;
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
!transactions ||
|
|
40
|
-
!Array.isArray(transactions) ||
|
|
41
|
-
transactions.length === 0
|
|
42
|
-
) {
|
|
43
|
-
throw errors.BAD_REQUEST({
|
|
44
|
-
message: "Transactions array is required and cannot be empty",
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Extract payer from the first transaction
|
|
49
|
-
let payer: string;
|
|
50
|
-
try {
|
|
51
|
-
const firstTransaction = VersionedTransaction.deserialize(
|
|
52
|
-
Buffer.from(transactions[0].serializedTransaction, "base64"),
|
|
53
|
-
);
|
|
54
|
-
payer = firstTransaction.message.staticAccountKeys[0].toBase58();
|
|
55
|
-
} catch {
|
|
56
|
-
throw errors.BAD_REQUEST({
|
|
57
|
-
message: "Failed to decode transaction to extract payer",
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (transactions.length > 5) {
|
|
62
|
-
throw errors.BAD_REQUEST({
|
|
63
|
-
message: "Maximum of 5 transactions allowed per batch",
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Simulate transactions before submission (except for sequential batches)
|
|
68
|
-
if (parallel || transactions.length === 1) {
|
|
69
|
-
const connection = new Connection(env.SOLANA_RPC_URL);
|
|
70
|
-
|
|
71
|
-
const simulationPromises = transactions.map(async (tx, index) => {
|
|
72
|
-
try {
|
|
73
|
-
const transaction = VersionedTransaction.deserialize(
|
|
74
|
-
Buffer.from(tx.serializedTransaction, "base64"),
|
|
75
|
-
);
|
|
76
|
-
const simulation = await connection.simulateTransaction(transaction);
|
|
77
|
-
|
|
78
|
-
if (simulation.value.err) {
|
|
79
|
-
const errorMessage =
|
|
80
|
-
typeof simulation.value.err === "string"
|
|
81
|
-
? simulation.value.err
|
|
82
|
-
: JSON.stringify(simulation.value.err);
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
index,
|
|
86
|
-
error: `Transaction ${
|
|
87
|
-
index + 1
|
|
88
|
-
} simulation failed: ${errorMessage}`,
|
|
89
|
-
logs: simulation.value.logs,
|
|
90
|
-
link: getExplorerUrl(transaction),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return { index, success: true };
|
|
94
|
-
} catch (err) {
|
|
95
|
-
return {
|
|
96
|
-
index,
|
|
97
|
-
error: `Transaction ${index + 1} deserialization failed: ${
|
|
98
|
-
err instanceof Error ? err.message : "Unknown error"
|
|
99
|
-
}`,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const simulationResults = await Promise.all(simulationPromises);
|
|
105
|
-
const failures = simulationResults.filter((result) => "error" in result);
|
|
106
|
-
|
|
107
|
-
if (failures.length > 0) {
|
|
108
|
-
const firstFailure = failures[0];
|
|
109
|
-
throw errors.SIMULATION_FAILED({
|
|
110
|
-
message: firstFailure.error || "Transaction simulation failed",
|
|
111
|
-
data: {
|
|
112
|
-
logs: "logs" in firstFailure ? firstFailure.logs : undefined,
|
|
113
|
-
link: "link" in firstFailure ? firstFailure.link : undefined,
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check for existing pending transaction with same tag+payer
|
|
120
|
-
if (tag) {
|
|
121
|
-
const existingBatch = await TransactionBatch.findOne({
|
|
122
|
-
where: {
|
|
123
|
-
tag,
|
|
124
|
-
payer,
|
|
125
|
-
status: "pending",
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
if (existingBatch) {
|
|
130
|
-
return {
|
|
131
|
-
batchId: existingBatch.id,
|
|
132
|
-
message: `Transaction with tag "${tag}" already exists and is pending`,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const serializedTransactions = transactions.map(
|
|
138
|
-
(tx) => tx.serializedTransaction,
|
|
139
|
-
);
|
|
140
|
-
const result = await submitTransactionBatch({
|
|
141
|
-
transactions: serializedTransactions,
|
|
142
|
-
parallel,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
if (result.error) {
|
|
146
|
-
throw errors.BAD_REQUEST({ message: result.error });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const cluster = getCluster();
|
|
150
|
-
|
|
151
|
-
// Use database transaction to ensure data consistency
|
|
152
|
-
const dbTransaction = await sequelize.transaction();
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
// Create the batch record
|
|
156
|
-
await TransactionBatch.create(
|
|
157
|
-
{
|
|
158
|
-
id: result.batchId,
|
|
159
|
-
parallel,
|
|
160
|
-
status: "pending",
|
|
161
|
-
submissionType: result.submissionType,
|
|
162
|
-
jitoBundleId: result.jitoBundleId,
|
|
163
|
-
cluster,
|
|
164
|
-
tag,
|
|
165
|
-
payer,
|
|
166
|
-
},
|
|
167
|
-
{ transaction: dbTransaction },
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Create individual transaction records
|
|
171
|
-
const pendingTransactionPromises = transactions.map(async (txData, i) => {
|
|
172
|
-
const signature = result.signatures?.[i] || null;
|
|
173
|
-
|
|
174
|
-
// Decode transaction to get blockhash
|
|
175
|
-
const transaction = VersionedTransaction.deserialize(
|
|
176
|
-
Buffer.from(txData.serializedTransaction, "base64"),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
return PendingTransaction.create(
|
|
180
|
-
{
|
|
181
|
-
signature: signature || `${result.batchId}-${i}`,
|
|
182
|
-
blockhash: transaction.message.recentBlockhash,
|
|
183
|
-
status: "pending",
|
|
184
|
-
type: txData.metadata?.type || "batch",
|
|
185
|
-
batchId: result.batchId,
|
|
186
|
-
payer,
|
|
187
|
-
metadata: txData.metadata,
|
|
188
|
-
serializedTransaction: txData.serializedTransaction,
|
|
189
|
-
},
|
|
190
|
-
{ transaction: dbTransaction },
|
|
191
|
-
);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await Promise.all(pendingTransactionPromises);
|
|
195
|
-
|
|
196
|
-
// Commit the transaction
|
|
197
|
-
await dbTransaction.commit();
|
|
198
|
-
} catch (error: unknown) {
|
|
199
|
-
// Rollback the transaction on any error
|
|
200
|
-
await dbTransaction.rollback();
|
|
201
|
-
|
|
202
|
-
// Handle unique constraint violation for tag+payer when status is pending
|
|
203
|
-
if (
|
|
204
|
-
(error as { name?: string })?.name ===
|
|
205
|
-
"SequelizeUniqueConstraintError" &&
|
|
206
|
-
tag
|
|
207
|
-
) {
|
|
208
|
-
throw errors.CONFLICT({
|
|
209
|
-
message: `A pending transaction with tag "${tag}" already exists for this payer`,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return { batchId: result.batchId };
|
|
216
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { submit } from "./procedures/submit";
|
|
2
|
-
import { get } from "./procedures/get";
|
|
3
|
-
import { resubmit } from "./procedures/resubmit";
|
|
4
|
-
import { getByPayer } from "./procedures/getByPayer";
|
|
5
|
-
import { getByPayerAndTag } from "./procedures/getByPayerAndTag";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Transactions router - handles transaction submission and status tracking.
|
|
9
|
-
*/
|
|
10
|
-
export const transactionsRouter = {
|
|
11
|
-
/** Submit a batch of transactions */
|
|
12
|
-
submit,
|
|
13
|
-
/** Get transaction batch status by ID */
|
|
14
|
-
get,
|
|
15
|
-
/** Resubmit a batch of pending transactions */
|
|
16
|
-
resubmit,
|
|
17
|
-
/** Get transaction batches by payer */
|
|
18
|
-
getByPayer,
|
|
19
|
-
/** Get transaction batches by payer and tag */
|
|
20
|
-
getByPayerAndTag,
|
|
21
|
-
};
|