@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,152 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetInstructionsInputSchema, TransactionDataSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import { env } from "@/lib/env";
|
|
5
|
-
import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
6
|
-
import {
|
|
7
|
-
populateMissingDraftInfo,
|
|
8
|
-
toVersionedTx,
|
|
9
|
-
withPriorityFees,
|
|
10
|
-
} from "@helium/spl-utils";
|
|
11
|
-
import {
|
|
12
|
-
generateTransactionTag,
|
|
13
|
-
TRANSACTION_TYPES,
|
|
14
|
-
} from "@/lib/utils/transaction-tags";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Get swap transaction instructions from Jupiter and build a transaction.
|
|
18
|
-
*/
|
|
19
|
-
export const getInstructions = publicProcedure
|
|
20
|
-
.route({ method: "POST", path: "/swap/instructions" })
|
|
21
|
-
.input(GetInstructionsInputSchema)
|
|
22
|
-
.output(TransactionDataSchema)
|
|
23
|
-
.errors({
|
|
24
|
-
JUPITER_ERROR: { message: "Failed to get swap instructions from Jupiter" },
|
|
25
|
-
})
|
|
26
|
-
.handler(async ({ input, errors }) => {
|
|
27
|
-
const {
|
|
28
|
-
quoteResponse,
|
|
29
|
-
userPublicKey,
|
|
30
|
-
destinationTokenAccount,
|
|
31
|
-
dynamicComputeUnitLimit,
|
|
32
|
-
prioritizationFeeLamports,
|
|
33
|
-
} = input;
|
|
34
|
-
|
|
35
|
-
// Get swap instructions from Jupiter
|
|
36
|
-
const instructionsResponse = await fetch(
|
|
37
|
-
`${env.JUPITER_API_URL}/swap/v1/swap-instructions`,
|
|
38
|
-
{
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: {
|
|
41
|
-
"Content-Type": "application/json",
|
|
42
|
-
"x-api-key": env.JUPITER_API_KEY,
|
|
43
|
-
},
|
|
44
|
-
body: JSON.stringify({
|
|
45
|
-
quoteResponse,
|
|
46
|
-
userPublicKey,
|
|
47
|
-
destinationTokenAccount,
|
|
48
|
-
dynamicComputeUnitLimit,
|
|
49
|
-
prioritizationFeeLamports: prioritizationFeeLamports || {
|
|
50
|
-
priorityLevelWithMaxLamports: {
|
|
51
|
-
maxLamports: 1000000,
|
|
52
|
-
priorityLevel: "medium",
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
}),
|
|
56
|
-
},
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (!instructionsResponse.ok) {
|
|
60
|
-
const errorText = await instructionsResponse.text();
|
|
61
|
-
console.error("Jupiter API error:", errorText);
|
|
62
|
-
throw errors.JUPITER_ERROR({
|
|
63
|
-
message: `Failed to get swap instructions from Jupiter: HTTP ${instructionsResponse.status}`,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const instructions = await instructionsResponse.json();
|
|
68
|
-
|
|
69
|
-
if (instructions.error) {
|
|
70
|
-
throw errors.JUPITER_ERROR({
|
|
71
|
-
message: `Jupiter API returned error: ${instructions.error}`,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Build the transaction using the same pattern
|
|
76
|
-
const deserializeInstruction = (instruction: {
|
|
77
|
-
programId: string;
|
|
78
|
-
accounts: { pubkey: string; isSigner: boolean; isWritable: boolean }[];
|
|
79
|
-
data: string;
|
|
80
|
-
}) => {
|
|
81
|
-
return new TransactionInstruction({
|
|
82
|
-
programId: new PublicKey(instruction.programId),
|
|
83
|
-
keys: instruction.accounts.map((key) => ({
|
|
84
|
-
pubkey: new PublicKey(key.pubkey),
|
|
85
|
-
isSigner: key.isSigner,
|
|
86
|
-
isWritable: key.isWritable,
|
|
87
|
-
})),
|
|
88
|
-
data: Buffer.from(instruction.data, "base64"),
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const jupIxs = [
|
|
93
|
-
...(instructions.setupInstructions
|
|
94
|
-
? instructions.setupInstructions.map(
|
|
95
|
-
(instruction: {
|
|
96
|
-
programId: string;
|
|
97
|
-
accounts: {
|
|
98
|
-
pubkey: string;
|
|
99
|
-
isSigner: boolean;
|
|
100
|
-
isWritable: boolean;
|
|
101
|
-
}[];
|
|
102
|
-
data: string;
|
|
103
|
-
}) => deserializeInstruction(instruction),
|
|
104
|
-
)
|
|
105
|
-
: []),
|
|
106
|
-
// Swap instruction
|
|
107
|
-
deserializeInstruction(instructions.swapInstruction),
|
|
108
|
-
// Cleanup instruction if present
|
|
109
|
-
...(instructions.cleanupInstruction
|
|
110
|
-
? [deserializeInstruction(instructions.cleanupInstruction)]
|
|
111
|
-
: []),
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
const draft = {
|
|
115
|
-
instructions: jupIxs,
|
|
116
|
-
feePayer: new PublicKey(userPublicKey),
|
|
117
|
-
addressLookupTableAddresses: instructions.addressLookupTableAddresses.map(
|
|
118
|
-
(address: string) => new PublicKey(address),
|
|
119
|
-
),
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const connection = new Connection(process.env.SOLANA_RPC_URL!);
|
|
123
|
-
const tx = toVersionedTx(
|
|
124
|
-
await populateMissingDraftInfo(connection, {
|
|
125
|
-
...draft,
|
|
126
|
-
instructions: await withPriorityFees({ ...draft, connection }),
|
|
127
|
-
}),
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
// Generate transaction tag
|
|
131
|
-
const tag = generateTransactionTag({
|
|
132
|
-
type: TRANSACTION_TYPES.SWAP,
|
|
133
|
-
userAddress: userPublicKey,
|
|
134
|
-
inputMint: quoteResponse.inputMint,
|
|
135
|
-
outputMint: quoteResponse.outputMint,
|
|
136
|
-
amount: quoteResponse.inAmount,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
transactions: [
|
|
141
|
-
{
|
|
142
|
-
serializedTransaction: Buffer.from(tx.serialize()).toString("base64"),
|
|
143
|
-
metadata: {
|
|
144
|
-
type: "swap",
|
|
145
|
-
description: `Swap ${quoteResponse.inAmount} ${quoteResponse.inputMint} for ${quoteResponse.outAmount} ${quoteResponse.outputMint}`,
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
parallel: false,
|
|
150
|
-
tag,
|
|
151
|
-
};
|
|
152
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetQuoteInputSchema, QuoteResponseSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import { env } from "@/lib/env";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get a quote for swapping tokens from Jupiter.
|
|
8
|
-
*/
|
|
9
|
-
export const getQuote = publicProcedure
|
|
10
|
-
.route({ method: "GET", path: "/swap/quote" })
|
|
11
|
-
.input(GetQuoteInputSchema)
|
|
12
|
-
.output(QuoteResponseSchema)
|
|
13
|
-
.errors({
|
|
14
|
-
JUPITER_ERROR: { message: "Failed to get quote from Jupiter" },
|
|
15
|
-
})
|
|
16
|
-
.handler(async ({ input, errors }) => {
|
|
17
|
-
const { inputMint, outputMint, amount, swapMode, slippageBps } = input;
|
|
18
|
-
|
|
19
|
-
// Get quote from Jupiter
|
|
20
|
-
const quoteUrl = new URL(`${env.JUPITER_API_URL}/swap/v1/quote`);
|
|
21
|
-
quoteUrl.searchParams.set("inputMint", inputMint);
|
|
22
|
-
quoteUrl.searchParams.set("outputMint", outputMint);
|
|
23
|
-
quoteUrl.searchParams.set("amount", amount);
|
|
24
|
-
quoteUrl.searchParams.set("swapMode", swapMode);
|
|
25
|
-
quoteUrl.searchParams.set("slippageBps", slippageBps.toString());
|
|
26
|
-
|
|
27
|
-
const quoteResponse = await fetch(quoteUrl.toString(), {
|
|
28
|
-
headers: {
|
|
29
|
-
"x-api-key": env.JUPITER_API_KEY,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (!quoteResponse.ok) {
|
|
34
|
-
const errorText = await quoteResponse.text();
|
|
35
|
-
console.error("Jupiter API error:", errorText);
|
|
36
|
-
throw errors.JUPITER_ERROR({
|
|
37
|
-
message: `Failed to get quote from Jupiter: HTTP ${quoteResponse.status}`,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const quote = await quoteResponse.json();
|
|
42
|
-
|
|
43
|
-
// Validate the response from Jupiter
|
|
44
|
-
const responseValidation = QuoteResponseSchema.safeParse(quote);
|
|
45
|
-
if (!responseValidation.success) {
|
|
46
|
-
console.error("Invalid Jupiter response:", responseValidation.error);
|
|
47
|
-
throw errors.JUPITER_ERROR({
|
|
48
|
-
message: "Invalid response from Jupiter API",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return responseValidation.data;
|
|
53
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetTokensInputSchema, TokenListOutputSchema, Token } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import { env } from "@/lib/env";
|
|
5
|
-
import { TOKEN_MINTS } from "@/lib/constants/tokens";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Get list of verified tokens available for swapping from Jupiter.
|
|
9
|
-
*/
|
|
10
|
-
export const getTokens = publicProcedure
|
|
11
|
-
.route({ method: "GET", path: "/swap/tokens" })
|
|
12
|
-
.input(GetTokensInputSchema)
|
|
13
|
-
.output(TokenListOutputSchema)
|
|
14
|
-
.errors({
|
|
15
|
-
JUPITER_ERROR: { message: "Failed to fetch tokens from Jupiter" },
|
|
16
|
-
})
|
|
17
|
-
.handler(async ({ input, errors }) => {
|
|
18
|
-
const { limit } = input;
|
|
19
|
-
|
|
20
|
-
// Use Jupiter's Token API V2 endpoint for verified tokens
|
|
21
|
-
const jupiterUrl = new URL(`${env.JUPITER_API_URL}/tokens/v2/tag`);
|
|
22
|
-
jupiterUrl.searchParams.set("query", "verified");
|
|
23
|
-
|
|
24
|
-
const jupiterResponse = await fetch(jupiterUrl.toString(), {
|
|
25
|
-
headers: {
|
|
26
|
-
"User-Agent": "my-helium-api/1.0",
|
|
27
|
-
"x-api-key": env.JUPITER_API_KEY,
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
if (!jupiterResponse.ok) {
|
|
32
|
-
const errorText = await jupiterResponse.text();
|
|
33
|
-
console.error("Jupiter Token API error:", errorText);
|
|
34
|
-
throw errors.JUPITER_ERROR({
|
|
35
|
-
message: `Failed to fetch tokens from Jupiter: HTTP ${jupiterResponse.status}`,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const jupiterTokens: {
|
|
40
|
-
id: string;
|
|
41
|
-
symbol: string;
|
|
42
|
-
name: string;
|
|
43
|
-
decimals: number;
|
|
44
|
-
icon?: string;
|
|
45
|
-
tags?: string[];
|
|
46
|
-
}[] = await jupiterResponse.json();
|
|
47
|
-
|
|
48
|
-
// Transform the V2 response format to our expected format
|
|
49
|
-
const validatedTokens: Token[] = jupiterTokens
|
|
50
|
-
.slice(0, limit)
|
|
51
|
-
.map((token) => ({
|
|
52
|
-
address: token.id,
|
|
53
|
-
symbol: token.symbol,
|
|
54
|
-
name: token.name,
|
|
55
|
-
decimals: token.decimals,
|
|
56
|
-
logoURI: token.icon,
|
|
57
|
-
tags: token.tags || [],
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
// Add HNT token at the top if it's not already in the list
|
|
61
|
-
const hntToken: Token = {
|
|
62
|
-
address: TOKEN_MINTS.HNT,
|
|
63
|
-
symbol: "HNT",
|
|
64
|
-
name: "Helium",
|
|
65
|
-
decimals: 8,
|
|
66
|
-
logoURI: "https://cryptologos.cc/logos/helium-hnt-logo.png",
|
|
67
|
-
tags: ["helium", "iot", "crypto"],
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Check if HNT is already in the list
|
|
71
|
-
const hasHNT = validatedTokens.some((token) => token.symbol === "HNT");
|
|
72
|
-
|
|
73
|
-
// Add HNT at the top if not present
|
|
74
|
-
if (!hasHNT) {
|
|
75
|
-
validatedTokens.unshift(hntToken);
|
|
76
|
-
} else {
|
|
77
|
-
// Move HNT to the top if it exists
|
|
78
|
-
const hntIndex = validatedTokens.findIndex(
|
|
79
|
-
(token) => token.symbol === "HNT",
|
|
80
|
-
);
|
|
81
|
-
if (hntIndex > 0) {
|
|
82
|
-
const hnt = validatedTokens.splice(hntIndex, 1)[0];
|
|
83
|
-
validatedTokens.unshift(hnt);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return { tokens: validatedTokens };
|
|
88
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { getTokens } from "./procedures/getTokens";
|
|
2
|
-
import { getQuote } from "./procedures/getQuote";
|
|
3
|
-
import { getInstructions } from "./procedures/getInstructions";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Swap router - handles token swap operations via Jupiter.
|
|
7
|
-
*/
|
|
8
|
-
export const swapRouter = {
|
|
9
|
-
/** Get list of verified tokens available for swapping */
|
|
10
|
-
getTokens,
|
|
11
|
-
/** Get a quote for swapping tokens */
|
|
12
|
-
getQuote,
|
|
13
|
-
/** Get swap transaction instructions */
|
|
14
|
-
getInstructions,
|
|
15
|
-
};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// Input Schemas
|
|
5
|
-
// ============================================================================
|
|
6
|
-
|
|
7
|
-
export const GetTokensInputSchema = z.object({
|
|
8
|
-
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export const GetQuoteInputSchema = z.object({
|
|
12
|
-
inputMint: z.string().min(1),
|
|
13
|
-
outputMint: z.string().min(1),
|
|
14
|
-
amount: z.string().min(1),
|
|
15
|
-
swapMode: z.enum(["ExactIn", "ExactOut"]).default("ExactIn"),
|
|
16
|
-
slippageBps: z.coerce.number().min(0).max(10000).default(50),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const QuoteResponseSchema = z
|
|
20
|
-
.object({
|
|
21
|
-
inputMint: z.string(),
|
|
22
|
-
inAmount: z.string(),
|
|
23
|
-
outputMint: z.string(),
|
|
24
|
-
outAmount: z.string(),
|
|
25
|
-
otherAmountThreshold: z.string(),
|
|
26
|
-
swapMode: z.string(),
|
|
27
|
-
slippageBps: z.number(),
|
|
28
|
-
platformFee: z.unknown().optional(),
|
|
29
|
-
priceImpactPct: z.string(),
|
|
30
|
-
routePlan: z.array(z.unknown()),
|
|
31
|
-
contextSlot: z.number().optional(),
|
|
32
|
-
timeTaken: z.number().optional(),
|
|
33
|
-
})
|
|
34
|
-
.passthrough(); // Allow additional fields from Jupiter that we don't explicitly define
|
|
35
|
-
|
|
36
|
-
export const GetInstructionsInputSchema = z.object({
|
|
37
|
-
quoteResponse: QuoteResponseSchema,
|
|
38
|
-
userPublicKey: z.string().min(1),
|
|
39
|
-
destinationTokenAccount: z.string().optional(),
|
|
40
|
-
dynamicComputeUnitLimit: z.boolean().default(true),
|
|
41
|
-
prioritizationFeeLamports: z
|
|
42
|
-
.object({
|
|
43
|
-
priorityLevelWithMaxLamports: z.object({
|
|
44
|
-
maxLamports: z.number().default(1000000),
|
|
45
|
-
priorityLevel: z.enum(["low", "medium", "high"]).default("medium"),
|
|
46
|
-
}),
|
|
47
|
-
})
|
|
48
|
-
.optional(),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// Output Schemas
|
|
53
|
-
// ============================================================================
|
|
54
|
-
|
|
55
|
-
export const TokenSchema = z.object({
|
|
56
|
-
address: z.string(),
|
|
57
|
-
symbol: z.string(),
|
|
58
|
-
name: z.string(),
|
|
59
|
-
decimals: z.number(),
|
|
60
|
-
logoURI: z.string().optional(),
|
|
61
|
-
tags: z.array(z.string()).optional(),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const TokenListOutputSchema = z.object({
|
|
65
|
-
tokens: z.array(TokenSchema),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
export const TransactionMetadataSchema = z
|
|
69
|
-
.object({
|
|
70
|
-
type: z.string(),
|
|
71
|
-
description: z.string(),
|
|
72
|
-
})
|
|
73
|
-
.catchall(z.unknown());
|
|
74
|
-
|
|
75
|
-
export const TransactionItemSchema = z.object({
|
|
76
|
-
serializedTransaction: z.string(),
|
|
77
|
-
metadata: TransactionMetadataSchema.optional(),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
export const TransactionDataSchema = z.object({
|
|
81
|
-
transactions: z.array(TransactionItemSchema),
|
|
82
|
-
parallel: z.boolean(),
|
|
83
|
-
tag: z.string().optional(),
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// ============================================================================
|
|
87
|
-
// Type Exports
|
|
88
|
-
// ============================================================================
|
|
89
|
-
|
|
90
|
-
export type GetTokensInput = z.infer<typeof GetTokensInputSchema>;
|
|
91
|
-
export type GetQuoteInput = z.infer<typeof GetQuoteInputSchema>;
|
|
92
|
-
export type GetInstructionsInput = z.infer<typeof GetInstructionsInputSchema>;
|
|
93
|
-
export type QuoteResponse = z.infer<typeof QuoteResponseSchema>;
|
|
94
|
-
export type Token = z.infer<typeof TokenSchema>;
|
|
95
|
-
export type TokenListOutput = z.infer<typeof TokenListOutputSchema>;
|
|
96
|
-
export type TransactionData = z.infer<typeof TransactionDataSchema>;
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import {
|
|
3
|
-
CreateHntAccountInputSchema,
|
|
4
|
-
CreateHntAccountOutputSchema,
|
|
5
|
-
} from "../schemas";
|
|
6
|
-
import { ORPCError } from "@orpc/server";
|
|
7
|
-
import { PublicKey, Connection } from "@solana/web3.js";
|
|
8
|
-
import {
|
|
9
|
-
createAssociatedTokenAccountIdempotentInstruction,
|
|
10
|
-
getAssociatedTokenAddressSync,
|
|
11
|
-
} from "@solana/spl-token";
|
|
12
|
-
import {
|
|
13
|
-
populateMissingDraftInfo,
|
|
14
|
-
toVersionedTx,
|
|
15
|
-
withPriorityFees,
|
|
16
|
-
HNT_MINT,
|
|
17
|
-
} from "@helium/spl-utils";
|
|
18
|
-
import { env } from "@/lib/env";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create an HNT token account for a wallet.
|
|
22
|
-
*/
|
|
23
|
-
export const createHntAccount = publicProcedure
|
|
24
|
-
.route({ method: "POST", path: "/tokens/{walletAddress}/hnt-account/create" })
|
|
25
|
-
.input(CreateHntAccountInputSchema)
|
|
26
|
-
.output(CreateHntAccountOutputSchema)
|
|
27
|
-
.errors({
|
|
28
|
-
BAD_REQUEST: { message: "Wallet address is required" },
|
|
29
|
-
})
|
|
30
|
-
.handler(async ({ input }) => {
|
|
31
|
-
const { walletAddress } = input;
|
|
32
|
-
|
|
33
|
-
if (!walletAddress) {
|
|
34
|
-
throw new ORPCError("BAD_REQUEST", {
|
|
35
|
-
message: "Wallet address is required",
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const wallet = new PublicKey(walletAddress);
|
|
40
|
-
|
|
41
|
-
// Get the associated token account address for HNT
|
|
42
|
-
const hntTokenAccount = getAssociatedTokenAddressSync(
|
|
43
|
-
HNT_MINT,
|
|
44
|
-
wallet,
|
|
45
|
-
true, // allowOwnerOffCurve
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
// Create instruction to create the associated token account
|
|
49
|
-
const createAccountInstruction =
|
|
50
|
-
createAssociatedTokenAccountIdempotentInstruction(
|
|
51
|
-
wallet, // payer
|
|
52
|
-
hntTokenAccount, // associated token account
|
|
53
|
-
wallet, // owner
|
|
54
|
-
HNT_MINT, // mint
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const draft = {
|
|
58
|
-
instructions: [createAccountInstruction],
|
|
59
|
-
feePayer: wallet,
|
|
60
|
-
addressLookupTableAddresses: [] as PublicKey[],
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const connection = new Connection(env.SOLANA_RPC_URL);
|
|
64
|
-
const tx = toVersionedTx(
|
|
65
|
-
await populateMissingDraftInfo(connection, {
|
|
66
|
-
...draft,
|
|
67
|
-
instructions: await withPriorityFees({ ...draft, connection }),
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
transactionData: {
|
|
73
|
-
transactions: [
|
|
74
|
-
{
|
|
75
|
-
serializedTransaction: Buffer.from(tx.serialize()).toString(
|
|
76
|
-
"base64",
|
|
77
|
-
),
|
|
78
|
-
metadata: {
|
|
79
|
-
type: "hnt-token-account",
|
|
80
|
-
description: "Create HNT token account",
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
parallel: false,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetBalancesInputSchema, TokenBalanceDataSchema } from "../schemas";
|
|
3
|
-
import { getTokenBalances } from "@/lib/queries/tokens";
|
|
4
|
-
import { ORPCError } from "@orpc/server";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get token balances for a wallet address.
|
|
8
|
-
*/
|
|
9
|
-
export const getBalances = publicProcedure
|
|
10
|
-
.route({ method: "GET", path: "/tokens/{walletAddress}" })
|
|
11
|
-
.input(GetBalancesInputSchema)
|
|
12
|
-
.output(TokenBalanceDataSchema)
|
|
13
|
-
.errors({
|
|
14
|
-
BAD_REQUEST: { message: "Wallet address is required" },
|
|
15
|
-
})
|
|
16
|
-
.handler(async ({ input }) => {
|
|
17
|
-
const { walletAddress } = input;
|
|
18
|
-
|
|
19
|
-
if (!walletAddress) {
|
|
20
|
-
throw new ORPCError("BAD_REQUEST", {
|
|
21
|
-
message: "Wallet address is required",
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const tokenBalances = await getTokenBalances({ walletAddress });
|
|
26
|
-
return tokenBalances;
|
|
27
|
-
});
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { TransferInputSchema, TransferOutputSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import { PublicKey, SystemProgram, Connection } from "@solana/web3.js";
|
|
5
|
-
import {
|
|
6
|
-
createAssociatedTokenAccountIdempotentInstruction,
|
|
7
|
-
createTransferCheckedInstruction,
|
|
8
|
-
getAssociatedTokenAddressSync,
|
|
9
|
-
} from "@solana/spl-token";
|
|
10
|
-
import {
|
|
11
|
-
populateMissingDraftInfo,
|
|
12
|
-
toVersionedTx,
|
|
13
|
-
withPriorityFees,
|
|
14
|
-
} from "@helium/spl-utils";
|
|
15
|
-
import {
|
|
16
|
-
generateTransactionTag,
|
|
17
|
-
TRANSACTION_TYPES,
|
|
18
|
-
} from "@/lib/utils/transaction-tags";
|
|
19
|
-
import { TOKEN_MINTS } from "@/lib/constants/tokens";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Create a transaction to transfer tokens (SOL or SPL tokens).
|
|
23
|
-
*/
|
|
24
|
-
export const transfer = publicProcedure
|
|
25
|
-
.route({ method: "POST", path: "/tokens/{walletAddress}/transfer" })
|
|
26
|
-
.input(TransferInputSchema)
|
|
27
|
-
.output(TransferOutputSchema)
|
|
28
|
-
.errors({
|
|
29
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
30
|
-
INSUFFICIENT_FUNDS: { message: "Insufficient balance" },
|
|
31
|
-
})
|
|
32
|
-
.handler(async ({ input, errors }) => {
|
|
33
|
-
const { walletAddress, mint, destination, amount, decimals } = input;
|
|
34
|
-
|
|
35
|
-
if (!destination || !amount) {
|
|
36
|
-
throw errors.BAD_REQUEST({
|
|
37
|
-
message: "Destination and amount are required",
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const feePayer = new PublicKey(walletAddress);
|
|
42
|
-
const destKey = new PublicKey(destination);
|
|
43
|
-
const connection = new Connection(process.env.SOLANA_RPC_URL!);
|
|
44
|
-
|
|
45
|
-
const isSol = !mint || mint === "SOL" || mint === TOKEN_MINTS.WSOL;
|
|
46
|
-
|
|
47
|
-
const instructions: (
|
|
48
|
-
| ReturnType<typeof SystemProgram.transfer>
|
|
49
|
-
| ReturnType<typeof createAssociatedTokenAccountIdempotentInstruction>
|
|
50
|
-
| ReturnType<typeof createTransferCheckedInstruction>
|
|
51
|
-
)[] = [];
|
|
52
|
-
|
|
53
|
-
if (isSol) {
|
|
54
|
-
// Native SOL transfer
|
|
55
|
-
const normalized = (amount || "").trim();
|
|
56
|
-
const lamports = Math.round(parseFloat(normalized) * 1e9);
|
|
57
|
-
if (!Number.isFinite(lamports) || lamports <= 0) {
|
|
58
|
-
throw errors.BAD_REQUEST({ message: "Amount must be greater than 0" });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Ensure sender has sufficient SOL balance before creating tx
|
|
62
|
-
const balance = await connection.getBalance(feePayer);
|
|
63
|
-
if (balance < lamports) {
|
|
64
|
-
throw errors.INSUFFICIENT_FUNDS({
|
|
65
|
-
message: "Insufficient SOL balance",
|
|
66
|
-
data: {
|
|
67
|
-
balance,
|
|
68
|
-
required: lamports,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
instructions.push(
|
|
74
|
-
SystemProgram.transfer({
|
|
75
|
-
fromPubkey: feePayer,
|
|
76
|
-
toPubkey: destKey,
|
|
77
|
-
lamports,
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
} else {
|
|
81
|
-
if (!decimals || decimals < 0) {
|
|
82
|
-
throw errors.BAD_REQUEST({ message: "Token decimals are required" });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const mintKey = new PublicKey(mint!);
|
|
86
|
-
const senderAta = getAssociatedTokenAddressSync(mintKey, feePayer, true);
|
|
87
|
-
const destAta = getAssociatedTokenAddressSync(mintKey, destKey, true);
|
|
88
|
-
const normalized = (amount || "").trim();
|
|
89
|
-
const rawAmount = Math.round(
|
|
90
|
-
parseFloat(normalized) * Math.pow(10, decimals),
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
if (!Number.isFinite(rawAmount) || rawAmount <= 0) {
|
|
94
|
-
throw errors.BAD_REQUEST({ message: "Amount must be greater than 0" });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Ensure destination ATA exists
|
|
98
|
-
instructions.push(
|
|
99
|
-
createAssociatedTokenAccountIdempotentInstruction(
|
|
100
|
-
feePayer,
|
|
101
|
-
destAta,
|
|
102
|
-
destKey,
|
|
103
|
-
mintKey,
|
|
104
|
-
),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
instructions.push(
|
|
108
|
-
createTransferCheckedInstruction(
|
|
109
|
-
senderAta,
|
|
110
|
-
mintKey,
|
|
111
|
-
destAta,
|
|
112
|
-
feePayer,
|
|
113
|
-
rawAmount,
|
|
114
|
-
decimals,
|
|
115
|
-
),
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const draft = {
|
|
120
|
-
instructions,
|
|
121
|
-
feePayer,
|
|
122
|
-
addressLookupTableAddresses: [] as PublicKey[],
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const tx = toVersionedTx(
|
|
126
|
-
await populateMissingDraftInfo(connection, {
|
|
127
|
-
...draft,
|
|
128
|
-
instructions: await withPriorityFees({ ...draft, connection }),
|
|
129
|
-
}),
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const tokenSymbol = isSol ? "SOL" : "Token";
|
|
133
|
-
|
|
134
|
-
const tag = generateTransactionTag({
|
|
135
|
-
type: TRANSACTION_TYPES.TOKEN_TRANSFER,
|
|
136
|
-
walletAddress,
|
|
137
|
-
destination,
|
|
138
|
-
mint: mint || "SOL",
|
|
139
|
-
amount,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
transactionData: {
|
|
144
|
-
transactions: [
|
|
145
|
-
{
|
|
146
|
-
serializedTransaction: Buffer.from(tx.serialize()).toString(
|
|
147
|
-
"base64",
|
|
148
|
-
),
|
|
149
|
-
metadata: {
|
|
150
|
-
type: "token_transfer",
|
|
151
|
-
description: `Transfer ${tokenSymbol}`,
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
parallel: false,
|
|
156
|
-
tag,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { getBalances } from "./procedures/getBalances";
|
|
2
|
-
import { transfer } from "./procedures/transfer";
|
|
3
|
-
import { createHntAccount } from "./procedures/createHntAccount";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Tokens router - handles all token-related operations.
|
|
7
|
-
*/
|
|
8
|
-
export const tokensRouter = {
|
|
9
|
-
/** Get token balances for a wallet */
|
|
10
|
-
getBalances,
|
|
11
|
-
/** Create a transaction to transfer tokens */
|
|
12
|
-
transfer,
|
|
13
|
-
/** Create an HNT token account for a wallet */
|
|
14
|
-
createHntAccount,
|
|
15
|
-
};
|