@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.
- 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 -157
- 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,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
|
-
});
|