@helium/blockchain-api 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.
Files changed (52) hide show
  1. package/README.md +24 -2
  2. package/package.json +31 -31
  3. package/types/README.md +24 -2
  4. package/types/generated/index.d.mts +2481 -0
  5. package/types/generated/index.d.ts +2481 -0
  6. package/types/generated/index.js +723 -0
  7. package/types/generated/index.mjs +614 -0
  8. package/types/index.ts +11 -8
  9. package/src/server/api/errors.ts +0 -152
  10. package/src/server/api/index.ts +0 -40
  11. package/src/server/api/procedures.ts +0 -144
  12. package/src/server/api/routers/fiat/router.ts +0 -709
  13. package/src/server/api/routers/fiat/schemas.ts +0 -157
  14. package/src/server/api/routers/health/router.ts +0 -41
  15. package/src/server/api/routers/hotspots/procedures/claimRewards.ts +0 -258
  16. package/src/server/api/routers/hotspots/procedures/createSplit.ts +0 -253
  17. package/src/server/api/routers/hotspots/procedures/deleteSplit.ts +0 -156
  18. package/src/server/api/routers/hotspots/procedures/getHotspots.ts +0 -31
  19. package/src/server/api/routers/hotspots/procedures/getPendingRewards.ts +0 -44
  20. package/src/server/api/routers/hotspots/procedures/getSplit.ts +0 -88
  21. package/src/server/api/routers/hotspots/procedures/transferHotspot.ts +0 -204
  22. package/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts +0 -201
  23. package/src/server/api/routers/hotspots/router.ts +0 -30
  24. package/src/server/api/routers/hotspots/schemas.ts +0 -182
  25. package/src/server/api/routers/swap/procedures/getInstructions.ts +0 -152
  26. package/src/server/api/routers/swap/procedures/getQuote.ts +0 -53
  27. package/src/server/api/routers/swap/procedures/getTokens.ts +0 -88
  28. package/src/server/api/routers/swap/router.ts +0 -15
  29. package/src/server/api/routers/swap/schemas.ts +0 -96
  30. package/src/server/api/routers/tokens/procedures/createHntAccount.ts +0 -87
  31. package/src/server/api/routers/tokens/procedures/getBalances.ts +0 -27
  32. package/src/server/api/routers/tokens/procedures/transfer.ts +0 -159
  33. package/src/server/api/routers/tokens/router.ts +0 -15
  34. package/src/server/api/routers/tokens/schemas.ts +0 -80
  35. package/src/server/api/routers/transactions/procedures/get.ts +0 -46
  36. package/src/server/api/routers/transactions/procedures/getByPayer.ts +0 -111
  37. package/src/server/api/routers/transactions/procedures/getByPayerAndTag.ts +0 -119
  38. package/src/server/api/routers/transactions/procedures/resubmit.ts +0 -68
  39. package/src/server/api/routers/transactions/procedures/submit.ts +0 -216
  40. package/src/server/api/routers/transactions/router.ts +0 -21
  41. package/src/server/api/routers/transactions/schemas.ts +0 -119
  42. package/src/server/api/routers/webhooks/router.ts +0 -75
  43. package/src/server/api/routers/welcomePacks/procedures/claim.ts +0 -157
  44. package/src/server/api/routers/welcomePacks/procedures/create.ts +0 -247
  45. package/src/server/api/routers/welcomePacks/procedures/deletePack.ts +0 -118
  46. package/src/server/api/routers/welcomePacks/procedures/get.ts +0 -36
  47. package/src/server/api/routers/welcomePacks/procedures/getByAddress.ts +0 -26
  48. package/src/server/api/routers/welcomePacks/procedures/invite.ts +0 -44
  49. package/src/server/api/routers/welcomePacks/procedures/list.ts +0 -27
  50. package/src/server/api/routers/welcomePacks/router.ts +0 -27
  51. package/src/server/api/routers/welcomePacks/schemas.ts +0 -135
  52. package/src/server/api/schemas.ts +0 -281
@@ -1,157 +0,0 @@
1
- import { z } from "zod";
2
-
3
- // ============================================================================
4
- // Input Schemas
5
- // ============================================================================
6
-
7
- export const InitKycInputSchema = z.object({
8
- type: z.enum(["individual", "business"]).optional(),
9
- });
10
-
11
- export const CreateBankAccountInputSchema = z.object({
12
- currency: z.string(),
13
- account_type: z.string(),
14
- bank_name: z.string(),
15
- account_name: z.string(),
16
- first_name: z.string().optional(),
17
- last_name: z.string().optional(),
18
- account_owner_name: z.string().optional(),
19
- business_name: z.string().optional(),
20
- account: z.object({
21
- account_number: z.string(),
22
- routing_number: z.string(),
23
- checking_or_savings: z.string(),
24
- }),
25
- address: z.object({
26
- street_line_1: z.string(),
27
- line2: z.string().optional(),
28
- city: z.string(),
29
- state: z.string(),
30
- postal_code: z.string(),
31
- country: z.string(),
32
- }),
33
- });
34
-
35
- export const GetBankAccountInputSchema = z.object({
36
- id: z.string(),
37
- });
38
-
39
- export const DeleteBankAccountInputSchema = z.object({
40
- id: z.number(),
41
- });
42
-
43
- export const GetSendQuoteInputSchema = z.object({
44
- id: z.string(),
45
- usdAmount: z.string(),
46
- });
47
-
48
- export const SendFundsInputSchema = z.object({
49
- id: z.string(),
50
- userAddress: z.string(),
51
- quoteResponse: z
52
- .object({
53
- inputMint: z.string(),
54
- inAmount: z.string(),
55
- outputMint: z.string(),
56
- outAmount: z.string(),
57
- otherAmountThreshold: z.string(),
58
- swapMode: z.string(),
59
- slippageBps: z.number(),
60
- platformFee: z.unknown().optional(),
61
- priceImpactPct: z.string(),
62
- routePlan: z.array(z.unknown()),
63
- contextSlot: z.number().optional(),
64
- timeTaken: z.number().optional(),
65
- })
66
- .passthrough(), // Allow additional fields from Jupiter
67
- });
68
-
69
- export const GetTransferInputSchema = z.object({
70
- id: z.string(),
71
- });
72
-
73
- export const UpdateTransferInputSchema = z.object({
74
- id: z.string(),
75
- solanaSignature: z.string(),
76
- });
77
-
78
- // ============================================================================
79
- // Output Schemas
80
- // ============================================================================
81
-
82
- export const KycStatusOutputSchema = z.object({
83
- kycStatus: z.string(),
84
- tosStatus: z.string(),
85
- tosLink: z.string().nullable(),
86
- kycLink: z.string().nullable(),
87
- kycLinkId: z.string().nullable(),
88
- accountType: z.string().optional(),
89
- rejectionReasons: z.array(z.string()).optional(),
90
- });
91
-
92
- export const FeesOutputSchema = z.object({
93
- developer_fee: z.string(),
94
- developer_fee_percent: z.number(),
95
- });
96
-
97
- export const BankAccountSchema = z
98
- .object({
99
- id: z.number().optional(),
100
- bridgeUserId: z.number().optional(),
101
- bridgeExternalAccountId: z.string().optional(),
102
- accountName: z.string().optional(),
103
- bankName: z.string().optional(),
104
- lastFourDigits: z.string().optional(),
105
- routingNumber: z.string().optional(),
106
- accountType: z.string().optional(),
107
- createdAt: z.union([z.string(), z.date()]).optional(),
108
- updatedAt: z.union([z.string(), z.date()]).optional(),
109
- })
110
- .passthrough();
111
-
112
- export const BankAccountListOutputSchema = z.array(BankAccountSchema);
113
-
114
- export const DeleteBankAccountOutputSchema = z.object({
115
- success: z.boolean(),
116
- });
117
-
118
- export const TransactionMetadataSchema = z
119
- .object({
120
- type: z.string(),
121
- description: z.string(),
122
- })
123
- .catchall(z.unknown());
124
-
125
- export const TransactionItemSchema = z.object({
126
- serializedTransaction: z.string(),
127
- metadata: TransactionMetadataSchema.optional(),
128
- });
129
-
130
- export const TransactionDataSchema = z.object({
131
- transactions: z.array(TransactionItemSchema),
132
- parallel: z.boolean(),
133
- tag: z.string().optional(),
134
- });
135
-
136
- export const SendFundsOutputSchema = z.object({
137
- bridgeTransfer: z.unknown(),
138
- transactionData: TransactionDataSchema,
139
- });
140
-
141
- export const UpdateTransferOutputSchema = z.object({
142
- success: z.boolean(),
143
- });
144
-
145
- // Quote response is pass-through from Jupiter
146
- export const QuoteOutputSchema = z.unknown();
147
-
148
- // ============================================================================
149
- // Type Exports
150
- // ============================================================================
151
-
152
- export type InitKycInput = z.infer<typeof InitKycInputSchema>;
153
- export type CreateBankAccountInput = z.infer<
154
- typeof CreateBankAccountInputSchema
155
- >;
156
- export type KycStatusOutput = z.infer<typeof KycStatusOutputSchema>;
157
- export type FeesOutput = z.infer<typeof FeesOutputSchema>;
@@ -1,41 +0,0 @@
1
- import { z } from "zod";
2
- import { publicProcedure } from "../../procedures";
3
- import { connectToDb } from "@/lib/utils/db";
4
-
5
- // ============================================================================
6
- // Schemas
7
- // ============================================================================
8
-
9
- const HealthResponseSchema = z.object({
10
- ok: z.boolean(),
11
- error: z.string().optional(),
12
- });
13
-
14
- // ============================================================================
15
- // Procedures
16
- // ============================================================================
17
-
18
- /**
19
- * Health check procedure.
20
- * Verifies API and database connectivity.
21
- */
22
- const check = publicProcedure
23
- .route({ method: "GET", path: "/health" })
24
- .output(HealthResponseSchema)
25
- .handler(async () => {
26
- try {
27
- await connectToDb();
28
- return { ok: true };
29
- } catch (error) {
30
- console.error("Health check failed:", error);
31
- return { ok: false, error: "Database connection failed" };
32
- }
33
- });
34
-
35
- // ============================================================================
36
- // Router Export
37
- // ============================================================================
38
-
39
- export const healthRouter = {
40
- check,
41
- };
@@ -1,258 +0,0 @@
1
- import { publicProcedure } from "../../../procedures";
2
- import { ClaimRewardsInputSchema, ClaimRewardsOutputSchema } from "../schemas";
3
- import { getSingleton } from "@helium/account-fetch-cache";
4
- import {
5
- getHotspotsByOwner,
6
- getNumRecipientsNeeded,
7
- } from "@/lib/queries/hotspots";
8
- import { env } from "@/lib/env";
9
- import { createSolanaConnection } from "@/lib/solana";
10
- import { init as initLd } from "@helium/lazy-distributor-sdk";
11
- import {
12
- formBulkTransactions,
13
- getBulkRewards,
14
- } from "@/utils/distributorOracle";
15
- import {
16
- LAMPORTS_PER_SOL,
17
- PublicKey,
18
- SystemProgram,
19
- TransactionInstruction,
20
- VersionedTransaction,
21
- } from "@solana/web3.js";
22
- import * as anchor from "@coral-xyz/anchor";
23
- import {
24
- customSignerKey,
25
- init as initTuktuk,
26
- nextAvailableTaskIds,
27
- taskKey,
28
- taskQueueAuthorityKey,
29
- } from "@helium/tuktuk-sdk";
30
- import {
31
- HELIUM_COMMON_LUT,
32
- HELIUM_COMMON_LUT_DEVNET,
33
- HNT_MINT,
34
- batchInstructionsToTxsWithPriorityFee,
35
- toVersionedTx,
36
- } from "@helium/spl-utils";
37
- import { HNT_LAZY_DISTRIBUTOR_ADDRESS } from "@/lib/constants/lazy-distributor";
38
- import { getAssociatedTokenAddressSync } from "@solana/spl-token";
39
-
40
- const MIN_RENT = 0.00089088;
41
- const RECIPIENT_RENT = 0.00242208;
42
- const ATA_RENT = 0.002039 * LAMPORTS_PER_SOL;
43
- const MIN_RENT_LAMPORTS = Math.ceil(MIN_RENT * LAMPORTS_PER_SOL);
44
- const RECIPIENT_RENT_LAMPORTS = Math.ceil(RECIPIENT_RENT * LAMPORTS_PER_SOL);
45
- const ATA_RENT_LAMPORTS = Math.ceil(ATA_RENT);
46
-
47
- const HPL_CRONS_PROGRAM_ID = new PublicKey(
48
- "hcrLPFgFUY6sCUKzqLWxXx5bntDiDCrAZVcrXfx9AHu",
49
- );
50
- const TASK_QUEUE_ID = new PublicKey(
51
- process.env.HPL_CRONS_TASK_QUEUE ||
52
- "H39gEszvsi6AT4rYBiJTuZHJSF5hMHy6CKGTd7wzhsg7",
53
- );
54
-
55
- /**
56
- * Create transactions to claim rewards for all hotspots in a wallet.
57
- * For wallets with 3 or fewer hotspots, returns direct claim transactions.
58
- * For larger wallets, creates a Tuktuk task to process claims.
59
- */
60
- export const claimRewards = publicProcedure
61
- .route({ method: "POST", path: "/hotspots/wallet/{walletAddress}/claim" })
62
- .input(ClaimRewardsInputSchema)
63
- .output(ClaimRewardsOutputSchema)
64
- .errors({
65
- BAD_REQUEST: { message: "Invalid request" },
66
- INSUFFICIENT_FUNDS: {
67
- message: "Insufficient SOL balance to fund claim task",
68
- },
69
- })
70
- .handler(async ({ input, errors }) => {
71
- const { walletAddress } = input;
72
-
73
- // Single query: fetch up to 3 hotspots and total count
74
- const hotspotsData = await getHotspotsByOwner({
75
- owner: walletAddress,
76
- page: 1,
77
- limit: 3,
78
- });
79
- const { total } = hotspotsData;
80
-
81
- // For small wallets, request direct claim transactions
82
- if (total <= 3) {
83
- const assets = hotspotsData.hotspots.map((h) => new PublicKey(h.asset));
84
- const entityKeys = hotspotsData.hotspots.map((h) => h.entityKey);
85
-
86
- // Initialize programs
87
- const { provider } = createSolanaConnection(walletAddress);
88
- const ldProgram = await initLd(provider);
89
-
90
- // Fetch oracle rewards for these entity keys
91
- const rewards = await getBulkRewards(
92
- ldProgram,
93
- new PublicKey(HNT_LAZY_DISTRIBUTOR_ADDRESS),
94
- entityKeys,
95
- );
96
-
97
- // Build and sign transactions via oracle
98
- const vtxs: VersionedTransaction[] = await formBulkTransactions({
99
- program: ldProgram,
100
- rewards,
101
- assets,
102
- lazyDistributor: new PublicKey(HNT_LAZY_DISTRIBUTOR_ADDRESS),
103
- assetEndpoint: env.ASSET_ENDPOINT,
104
- skipOracleSign: false,
105
- });
106
-
107
- const singleton = await getSingleton(provider.connection);
108
- singleton.close();
109
-
110
- const txs = vtxs.map((tx) =>
111
- Buffer.from(tx.serialize()).toString("base64"),
112
- );
113
-
114
- return {
115
- transactionData: {
116
- transactions: txs.map((serialized) => ({
117
- serializedTransaction: serialized,
118
- metadata: {
119
- type: "claim_rewards",
120
- description: "Claim hotspot rewards",
121
- },
122
- })),
123
- parallel: true,
124
- tag: `claim_rewards:${walletAddress}`,
125
- },
126
- };
127
- }
128
-
129
- // For larger wallets, queue a Tuktuk claim task via HPL Crons
130
- const { provider } = createSolanaConnection(walletAddress);
131
- anchor.setProvider(provider);
132
-
133
- const tuktukProgram = await initTuktuk(provider);
134
- const taskQueueAcc =
135
- await tuktukProgram.account.taskQueueV0.fetch(TASK_QUEUE_ID);
136
- const [taskId] = nextAvailableTaskIds(taskQueueAcc.taskBitmap, 1);
137
-
138
- const queueAuthority = PublicKey.findProgramAddressSync(
139
- [Buffer.from("queue_authority")],
140
- HPL_CRONS_PROGRAM_ID,
141
- )[0];
142
-
143
- const idl = await anchor.Program.fetchIdl(HPL_CRONS_PROGRAM_ID, provider);
144
- const hplCronsProgram = new anchor.Program(
145
- idl as anchor.Idl,
146
- provider,
147
- ) as anchor.Program<anchor.Idl>;
148
-
149
- const instructions: TransactionInstruction[] = [];
150
-
151
- const pdaWallet = customSignerKey(TASK_QUEUE_ID, [
152
- Buffer.from("claim_payer"),
153
- new PublicKey(walletAddress).toBuffer(),
154
- ])[0];
155
- const pdaWalletBalanceLamports =
156
- await provider.connection.getBalance(pdaWallet);
157
- const ata = getAssociatedTokenAddressSync(
158
- HNT_MINT,
159
- new PublicKey(walletAddress),
160
- true,
161
- );
162
- const minCrankReward = taskQueueAcc?.minCrankReward?.toNumber() || 10000;
163
- const account = await provider.connection.getAccountInfo(ata);
164
- const pdaWalletFundingNeededLamports =
165
- MIN_RENT_LAMPORTS +
166
- (account ? 0 : ATA_RENT_LAMPORTS) +
167
- // Actual claim txs
168
- 20000 * (total || 1) +
169
- // Requeue transactions (5 queues per tx)
170
- minCrankReward * Math.ceil((total || 1) / 5);
171
- const pdaWalletLamportsShortfall = Math.max(
172
- 0,
173
- pdaWalletFundingNeededLamports - pdaWalletBalanceLamports,
174
- );
175
-
176
- const hotspotsNeedingRecipient =
177
- await getNumRecipientsNeeded(walletAddress);
178
- console.log(
179
- `[PDA WALLET ${pdaWallet.toBase58()}] Hotspots needing recipient: ${hotspotsNeedingRecipient}, shortfall: ${pdaWalletLamportsShortfall}`,
180
- );
181
-
182
- if (pdaWalletLamportsShortfall > 0 || hotspotsNeedingRecipient > 0) {
183
- const requiredLamports =
184
- pdaWalletLamportsShortfall +
185
- hotspotsNeedingRecipient * RECIPIENT_RENT_LAMPORTS;
186
- // Ensure the user's wallet has enough SOL to fund PDA and recipients before returning tx
187
- const senderBalance = await provider.connection.getBalance(
188
- new PublicKey(walletAddress),
189
- );
190
- if (senderBalance < requiredLamports) {
191
- throw errors.INSUFFICIENT_FUNDS({
192
- message: "Insufficient SOL balance to fund claim task",
193
- data: {
194
- balance: senderBalance,
195
- required: requiredLamports,
196
- },
197
- });
198
- }
199
- instructions.push(
200
- SystemProgram.transfer({
201
- fromPubkey: new PublicKey(walletAddress),
202
- toPubkey: pdaWallet,
203
- lamports: requiredLamports,
204
- }),
205
- );
206
- }
207
-
208
- const ix = await hplCronsProgram.methods
209
- .queueWalletClaimV0({ freeTaskId: taskId })
210
- .accountsStrict({
211
- task: taskKey(TASK_QUEUE_ID, taskId)[0],
212
- wallet: new PublicKey(walletAddress),
213
- taskQueue: TASK_QUEUE_ID,
214
- payer: provider.wallet.publicKey,
215
- systemProgram: SystemProgram.programId,
216
- queueAuthority,
217
- tuktukProgram: tuktukProgram.programId,
218
- pdaWallet: customSignerKey(TASK_QUEUE_ID, [
219
- Buffer.from("claim_payer"),
220
- new PublicKey(walletAddress).toBuffer(),
221
- ])[0],
222
- taskQueueAuthority: taskQueueAuthorityKey(
223
- TASK_QUEUE_ID,
224
- queueAuthority,
225
- )[0],
226
- })
227
- .instruction();
228
-
229
- instructions.push(ix);
230
- const vtxs = (
231
- await batchInstructionsToTxsWithPriorityFee(provider, instructions, {
232
- addressLookupTableAddresses: [
233
- process.env.NEXT_PUBLIC_SOLANA_CLUSTER?.trim() === "devnet"
234
- ? HELIUM_COMMON_LUT_DEVNET
235
- : HELIUM_COMMON_LUT,
236
- ],
237
- })
238
- ).map((tx) => toVersionedTx(tx));
239
-
240
- const txs: Array<string> = vtxs.map((tx) =>
241
- Buffer.from(tx.serialize()).toString("base64"),
242
- );
243
-
244
- return {
245
- transactionData: {
246
- transactions: txs.map((serialized) => ({
247
- serializedTransaction: serialized,
248
- metadata: {
249
- type: "queue_wallet_claim",
250
- description: "Queue wallet claim task via Tuktuk",
251
- taskIds: [taskId],
252
- },
253
- })),
254
- parallel: true,
255
- tag: `claim_rewards_tuktuk:${walletAddress}`,
256
- },
257
- };
258
- });
@@ -1,253 +0,0 @@
1
- import { publicProcedure } from "../../../procedures";
2
- import { CreateSplitInputSchema, CreateSplitOutputSchema } from "../schemas";
3
- import { ORPCError } from "@orpc/server";
4
- import { env } from "@/lib/env";
5
- import { createSolanaConnection } from "@/lib/solana";
6
- import { connectToDb } from "@/lib/utils/db";
7
- import { scheduleToUtcCron } from "@/lib/utils/misc";
8
- import { getAssetIdFromPubkey } from "@/lib/utils/hotspot-helpers";
9
- import {
10
- initializeCompressionRecipient,
11
- init as initLd,
12
- recipientKey,
13
- updateCompressionDestination,
14
- } from "@helium/lazy-distributor-sdk";
15
- import { init as initMiniFanout } from "@helium/mini-fanout-sdk";
16
- import {
17
- batchInstructionsToTxsWithPriorityFee,
18
- HELIUM_COMMON_LUT,
19
- HELIUM_COMMON_LUT_DEVNET,
20
- HNT_MINT,
21
- toVersionedTx,
22
- } from "@helium/spl-utils";
23
- import {
24
- init as initTuktuk,
25
- nextAvailableTaskIds,
26
- taskKey,
27
- } from "@helium/tuktuk-sdk";
28
- import {
29
- PublicKey,
30
- SystemProgram,
31
- TransactionInstruction,
32
- } from "@solana/web3.js";
33
- import BN from "bn.js";
34
- import {
35
- generateTransactionTag,
36
- TRANSACTION_TYPES,
37
- } from "@/lib/utils/transaction-tags";
38
- import {
39
- getCluster,
40
- getJitoTipTransaction,
41
- shouldUseJitoBundle,
42
- } from "@/lib/utils/transaction-submission";
43
-
44
- const TASK_QUEUE_ID = new PublicKey(
45
- "H39gEszvsi6AT4rYBiJTuZHJSF5hMHy6CKGTd7wzhsg7",
46
- );
47
- const FANOUT_FUNDING_AMOUNT = 10000000; // 0.01 SOL
48
-
49
- /**
50
- * Create a split configuration for a hotspot with reward distribution.
51
- */
52
- export const createSplit = publicProcedure
53
- .route({
54
- method: "POST",
55
- path: "/hotspots/wallet/{walletAddress}/{hotspotPubkey}/split",
56
- })
57
- .input(CreateSplitInputSchema)
58
- .output(CreateSplitOutputSchema)
59
- .errors({
60
- NOT_FOUND: { message: "Hotspot not found" },
61
- BAD_REQUEST: { message: "Invalid request" },
62
- INSUFFICIENT_FUNDS: {
63
- message: "Insufficient SOL balance to fund split setup",
64
- },
65
- })
66
- .handler(async ({ input, errors }) => {
67
- const {
68
- walletAddress,
69
- hotspotPubkey,
70
- rewardsSplit,
71
- schedule,
72
- lazyDistributor,
73
- } = input;
74
-
75
- await connectToDb();
76
-
77
- // Resolve hotspot pubkey to asset ID
78
- const assetId = await getAssetIdFromPubkey(hotspotPubkey);
79
- if (!assetId) {
80
- throw errors.NOT_FOUND({ message: "Hotspot not found" });
81
- }
82
-
83
- if (!rewardsSplit?.length) {
84
- throw errors.BAD_REQUEST({
85
- message: "At least one reward split is required",
86
- });
87
- }
88
-
89
- if (!schedule?.frequency || !schedule?.time || !schedule?.timezone) {
90
- throw errors.BAD_REQUEST({
91
- message: "Schedule frequency, time, and timezone are required",
92
- });
93
- }
94
-
95
- // Build connection and programs
96
- const { provider, wallet } = createSolanaConnection(walletAddress);
97
- const miniFanoutProgram = await initMiniFanout(provider);
98
- const tuktukProgram = await initTuktuk(provider);
99
- const ldProgram = await initLd(provider);
100
-
101
- // Convert schedule to UTC cron string
102
- const rewardsSchedule = scheduleToUtcCron(schedule);
103
-
104
- // Ensure Lazy Distributor Recipient exists for the asset
105
- const recipientK = recipientKey(
106
- new PublicKey(lazyDistributor),
107
- new PublicKey(assetId),
108
- )[0];
109
- const recipientAcc =
110
- await ldProgram.account.recipientV0.fetchNullable(recipientK);
111
- const instructions: TransactionInstruction[] = [];
112
-
113
- if (!recipientAcc) {
114
- instructions.push(
115
- await (
116
- await initializeCompressionRecipient({
117
- program: ldProgram,
118
- assetId: new PublicKey(assetId),
119
- payer: wallet.publicKey,
120
- assetEndpoint: env.SOLANA_RPC_URL,
121
- lazyDistributor: new PublicKey(lazyDistributor),
122
- })
123
- ).instruction(),
124
- );
125
- }
126
-
127
- const oracleSigner = new PublicKey(process.env.ORACLE_SIGNER!);
128
- const oracleUrl = process.env.ORACLE_URL!;
129
-
130
- const { instruction: initIx, pubkeys } = await miniFanoutProgram.methods
131
- .initializeMiniFanoutV0({
132
- seed: new PublicKey(assetId).toBuffer(),
133
- shares: rewardsSplit.map((split) => ({
134
- wallet: new PublicKey(split.address),
135
- share:
136
- split.type === "fixed"
137
- ? { fixed: { amount: new BN(split.amount) } }
138
- : { share: { amount: split.amount } },
139
- })),
140
- schedule: rewardsSchedule,
141
- preTask: {
142
- remoteV0: {
143
- url: `${oracleUrl}/v1/tuktuk/asset/${assetId}`,
144
- signer: oracleSigner,
145
- },
146
- },
147
- })
148
- .accounts({
149
- payer: wallet.publicKey,
150
- owner: wallet.publicKey,
151
- taskQueue: TASK_QUEUE_ID,
152
- rentRefund: wallet.publicKey,
153
- mint: HNT_MINT,
154
- })
155
- .prepare();
156
- instructions.push(initIx);
157
-
158
- // Ensure the user's wallet has enough SOL to fund the mini fanout rent
159
- const balance = await provider.connection.getBalance(
160
- new PublicKey(walletAddress),
161
- );
162
- if (balance < FANOUT_FUNDING_AMOUNT) {
163
- throw errors.INSUFFICIENT_FUNDS({
164
- message: "Insufficient SOL balance to fund split setup",
165
- data: {
166
- balance,
167
- required: FANOUT_FUNDING_AMOUNT,
168
- },
169
- });
170
- }
171
-
172
- instructions.push(
173
- SystemProgram.transfer({
174
- fromPubkey: wallet.publicKey,
175
- toPubkey: pubkeys.miniFanout!,
176
- lamports: FANOUT_FUNDING_AMOUNT,
177
- }),
178
- );
179
-
180
- const taskQueueAcc =
181
- await tuktukProgram.account.taskQueueV0.fetchNullable(TASK_QUEUE_ID);
182
- const [taskId, preTaskId] = nextAvailableTaskIds(
183
- taskQueueAcc!.taskBitmap,
184
- 2,
185
- );
186
-
187
- // Schedule a task for the mini fanout
188
- const scheduleIx = await (
189
- await miniFanoutProgram.methods
190
- .scheduleTaskV0({
191
- program: miniFanoutProgram,
192
- miniFanout: pubkeys.miniFanout!,
193
- taskId,
194
- preTaskId,
195
- })
196
- .accounts({
197
- taskQueue: TASK_QUEUE_ID,
198
- payer: wallet.publicKey,
199
- miniFanout: pubkeys.miniFanout!,
200
- task: taskKey(TASK_QUEUE_ID, taskId)[0],
201
- preTask: taskKey(TASK_QUEUE_ID, preTaskId)[0],
202
- nextTask: pubkeys.miniFanout!,
203
- nextPreTask: pubkeys.miniFanout!,
204
- })
205
- ).instruction();
206
- instructions.push(scheduleIx);
207
-
208
- // Point hotspot rewards destination to the mini fanout
209
- const setRecipientIx = await (
210
- await updateCompressionDestination({
211
- program: ldProgram,
212
- assetId: new PublicKey(assetId),
213
- lazyDistributor: new PublicKey(lazyDistributor),
214
- destination: pubkeys.miniFanout!,
215
- })
216
- ).instruction();
217
- instructions.push(setRecipientIx);
218
-
219
- const txs = (
220
- await batchInstructionsToTxsWithPriorityFee(provider, instructions, {
221
- addressLookupTableAddresses: [
222
- process.env.NEXT_PUBLIC_SOLANA_CLUSTER === "devnet"
223
- ? HELIUM_COMMON_LUT_DEVNET
224
- : HELIUM_COMMON_LUT,
225
- ],
226
- })
227
- ).map((tx) => toVersionedTx(tx));
228
-
229
- const tag = generateTransactionTag({
230
- type: TRANSACTION_TYPES.ADD_SPLIT,
231
- walletAddress,
232
- assetId,
233
- lazyDistributor,
234
- });
235
-
236
- if (shouldUseJitoBundle(txs.length, getCluster())) {
237
- txs.push(await getJitoTipTransaction(new PublicKey(walletAddress)));
238
- }
239
-
240
- return {
241
- transactionData: {
242
- transactions: txs.map((tx) => ({
243
- serializedTransaction: Buffer.from(tx.serialize()).toString("base64"),
244
- metadata: {
245
- type: "add_split",
246
- description: "Create split",
247
- },
248
- })),
249
- parallel: true,
250
- tag,
251
- },
252
- };
253
- });