@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,156 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { DeleteSplitInputSchema, DeleteSplitOutputSchema } from "../schemas";
|
|
3
|
-
import { ORPCError } from "@orpc/server";
|
|
4
|
-
import { connectToDb } from "@/lib/utils/db";
|
|
5
|
-
import { AssetOwner } from "@/lib/models/hotspot";
|
|
6
|
-
import { MiniFanout } from "@/lib/models/mini-fanout";
|
|
7
|
-
import { Recipient } from "@/lib/models/recipient";
|
|
8
|
-
import { createSolanaConnection } from "@/lib/solana";
|
|
9
|
-
import { getAssetIdFromPubkey } from "@/lib/utils/hotspot-helpers";
|
|
10
|
-
import {
|
|
11
|
-
init as initLd,
|
|
12
|
-
updateCompressionDestination,
|
|
13
|
-
} from "@helium/lazy-distributor-sdk";
|
|
14
|
-
import { init as initMiniFanout } from "@helium/mini-fanout-sdk";
|
|
15
|
-
import {
|
|
16
|
-
HELIUM_COMMON_LUT,
|
|
17
|
-
HELIUM_COMMON_LUT_DEVNET,
|
|
18
|
-
populateMissingDraftInfo,
|
|
19
|
-
toVersionedTx,
|
|
20
|
-
withPriorityFees,
|
|
21
|
-
} from "@helium/spl-utils";
|
|
22
|
-
import { init as initTuktuk } from "@helium/tuktuk-sdk";
|
|
23
|
-
import { PublicKey } from "@solana/web3.js";
|
|
24
|
-
import {
|
|
25
|
-
generateTransactionTag,
|
|
26
|
-
TRANSACTION_TYPES,
|
|
27
|
-
} from "@/lib/utils/transaction-tags";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Remove the split configuration from a hotspot.
|
|
31
|
-
*/
|
|
32
|
-
export const deleteSplit = publicProcedure
|
|
33
|
-
.route({
|
|
34
|
-
method: "DELETE",
|
|
35
|
-
path: "/hotspots/wallet/{walletAddress}/{hotspotPubkey}/split",
|
|
36
|
-
})
|
|
37
|
-
.input(DeleteSplitInputSchema)
|
|
38
|
-
.output(DeleteSplitOutputSchema)
|
|
39
|
-
.errors({
|
|
40
|
-
NOT_FOUND: { message: "Hotspot not found" },
|
|
41
|
-
NO_SPLIT: { message: "Hotspot does not have a split" },
|
|
42
|
-
})
|
|
43
|
-
.handler(async ({ input, errors }) => {
|
|
44
|
-
const { walletAddress, hotspotPubkey } = input;
|
|
45
|
-
|
|
46
|
-
await connectToDb();
|
|
47
|
-
|
|
48
|
-
// Resolve hotspot pubkey to asset ID
|
|
49
|
-
const assetId = await getAssetIdFromPubkey(hotspotPubkey);
|
|
50
|
-
if (!assetId) {
|
|
51
|
-
throw errors.NOT_FOUND({ message: "Hotspot not found" });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Find the hotspot
|
|
55
|
-
const assetOwner = await AssetOwner.findOne({
|
|
56
|
-
where: { asset: assetId },
|
|
57
|
-
include: [
|
|
58
|
-
{
|
|
59
|
-
model: Recipient,
|
|
60
|
-
as: "recipient",
|
|
61
|
-
required: true,
|
|
62
|
-
include: [
|
|
63
|
-
{
|
|
64
|
-
model: MiniFanout,
|
|
65
|
-
as: "split",
|
|
66
|
-
required: true,
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
if (!assetOwner) {
|
|
74
|
-
throw errors.NOT_FOUND({ message: "Hotspot not found" });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!assetOwner.recipient?.split) {
|
|
78
|
-
throw errors.NO_SPLIT({ message: "Hotspot does not have a split" });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { provider, connection } = createSolanaConnection(walletAddress);
|
|
82
|
-
const program = await initMiniFanout(provider);
|
|
83
|
-
const miniFanoutK = new PublicKey(assetOwner.recipient.split.address);
|
|
84
|
-
const miniFanout =
|
|
85
|
-
await program.account.miniFanoutV0.fetchNullable(miniFanoutK);
|
|
86
|
-
|
|
87
|
-
if (!miniFanout) {
|
|
88
|
-
throw errors.NOT_FOUND({ message: "Fanout not found" });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const tuktukProgram = await initTuktuk(provider);
|
|
92
|
-
|
|
93
|
-
// Create the transaction to remove the mini fanout
|
|
94
|
-
const task = miniFanout.nextTask.equals(miniFanoutK)
|
|
95
|
-
? null
|
|
96
|
-
: await tuktukProgram.account.taskV0.fetchNullable(miniFanout.nextTask);
|
|
97
|
-
|
|
98
|
-
const closeIx = await program.methods
|
|
99
|
-
.closeMiniFanoutV0()
|
|
100
|
-
.accounts({
|
|
101
|
-
miniFanout: miniFanoutK,
|
|
102
|
-
taskRentRefund: task?.rentRefund || walletAddress,
|
|
103
|
-
})
|
|
104
|
-
.instruction();
|
|
105
|
-
|
|
106
|
-
const ldProgram = await initLd(provider);
|
|
107
|
-
const setRecipientIx = await (
|
|
108
|
-
await updateCompressionDestination({
|
|
109
|
-
program: ldProgram,
|
|
110
|
-
assetId: new PublicKey(assetId),
|
|
111
|
-
lazyDistributor: new PublicKey(assetOwner.recipient.lazyDistributor),
|
|
112
|
-
destination: null,
|
|
113
|
-
})
|
|
114
|
-
).instruction();
|
|
115
|
-
|
|
116
|
-
const draft = {
|
|
117
|
-
instructions: [closeIx, setRecipientIx],
|
|
118
|
-
feePayer: new PublicKey(walletAddress),
|
|
119
|
-
addressLookupTableAddresses: [
|
|
120
|
-
process.env.NEXT_PUBLIC_SOLANA_CLUSTER === "devnet"
|
|
121
|
-
? HELIUM_COMMON_LUT_DEVNET
|
|
122
|
-
: HELIUM_COMMON_LUT,
|
|
123
|
-
],
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const tx = toVersionedTx(
|
|
127
|
-
await populateMissingDraftInfo(connection, {
|
|
128
|
-
...draft,
|
|
129
|
-
instructions: await withPriorityFees({ ...draft, connection }),
|
|
130
|
-
}),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const tag = generateTransactionTag({
|
|
134
|
-
type: TRANSACTION_TYPES.REMOVE_SPLIT,
|
|
135
|
-
walletAddress,
|
|
136
|
-
assetId,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
transactionData: {
|
|
141
|
-
transactions: [
|
|
142
|
-
{
|
|
143
|
-
serializedTransaction: Buffer.from(tx.serialize()).toString(
|
|
144
|
-
"base64",
|
|
145
|
-
),
|
|
146
|
-
metadata: {
|
|
147
|
-
type: "remove_split",
|
|
148
|
-
description: "Remove split",
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
parallel: true,
|
|
153
|
-
tag,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetHotspotsInputSchema, HotspotsDataSchema } from "../schemas";
|
|
3
|
-
import { getHotspotsByOwner } from "@/lib/queries/hotspots";
|
|
4
|
-
import { ORPCError } from "@orpc/server";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get hotspots by wallet address with optional filtering and pagination.
|
|
8
|
-
*/
|
|
9
|
-
export const getHotspots = publicProcedure
|
|
10
|
-
.route({ method: "GET", path: "/hotspots/wallet/{walletAddress}" })
|
|
11
|
-
.input(GetHotspotsInputSchema)
|
|
12
|
-
.output(HotspotsDataSchema)
|
|
13
|
-
.errors({
|
|
14
|
-
BAD_REQUEST: { message: "Invalid wallet address" },
|
|
15
|
-
})
|
|
16
|
-
.handler(async ({ input }) => {
|
|
17
|
-
const { walletAddress, type, page, limit } = input;
|
|
18
|
-
|
|
19
|
-
if (!walletAddress || walletAddress.length < 32) {
|
|
20
|
-
throw new ORPCError("BAD_REQUEST", { message: "Invalid wallet address" });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const hotspots = await getHotspotsByOwner({
|
|
24
|
-
owner: walletAddress,
|
|
25
|
-
type,
|
|
26
|
-
page,
|
|
27
|
-
limit,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return hotspots;
|
|
31
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import {
|
|
3
|
-
GetPendingRewardsInputSchema,
|
|
4
|
-
PendingRewardsOutputSchema,
|
|
5
|
-
} from "../schemas";
|
|
6
|
-
import { ORPCError } from "@orpc/server";
|
|
7
|
-
import { env } from "@/lib/env";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Get pending rewards for all hotspots in a wallet.
|
|
11
|
-
* Proxies to the oracle's rewards endpoint.
|
|
12
|
-
*/
|
|
13
|
-
export const getPendingRewards = publicProcedure
|
|
14
|
-
.route({
|
|
15
|
-
method: "GET",
|
|
16
|
-
path: "/hotspots/wallet/{walletAddress}/pending-rewards",
|
|
17
|
-
})
|
|
18
|
-
.input(GetPendingRewardsInputSchema)
|
|
19
|
-
.output(PendingRewardsOutputSchema)
|
|
20
|
-
.errors({
|
|
21
|
-
BAD_REQUEST: { message: "Invalid wallet address" },
|
|
22
|
-
ORACLE_ERROR: { message: "Oracle request failed" },
|
|
23
|
-
})
|
|
24
|
-
.handler(async ({ input, errors }) => {
|
|
25
|
-
const { walletAddress } = input;
|
|
26
|
-
|
|
27
|
-
if (!walletAddress || walletAddress.length < 32) {
|
|
28
|
-
throw new ORPCError("BAD_REQUEST", { message: "Invalid wallet address" });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const oracleBase = env.ORACLE_URL;
|
|
32
|
-
const url = new URL(`${oracleBase}/rewards`);
|
|
33
|
-
url.searchParams.set("destination", walletAddress);
|
|
34
|
-
|
|
35
|
-
const oracleRes = await fetch(url.toString());
|
|
36
|
-
if (!oracleRes.ok) {
|
|
37
|
-
throw errors.ORACLE_ERROR({
|
|
38
|
-
message: `Oracle request failed (${oracleRes.status})`,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const data = await oracleRes.json();
|
|
43
|
-
return data;
|
|
44
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import { GetSplitInputSchema, SplitResponseSchema } from "../schemas";
|
|
3
|
-
import { connectToDb } from "@/lib/utils/db";
|
|
4
|
-
import { AssetOwner } from "@/lib/models/hotspot";
|
|
5
|
-
import { MiniFanout } from "@/lib/models/mini-fanout";
|
|
6
|
-
import { Recipient } from "@/lib/models/recipient";
|
|
7
|
-
import { getAssetIdFromPubkey } from "@/lib/utils/hotspot-helpers";
|
|
8
|
-
|
|
9
|
-
interface MiniFanoutShare {
|
|
10
|
-
wallet: string;
|
|
11
|
-
delegate: string;
|
|
12
|
-
share?: {
|
|
13
|
-
fixed?: { amount?: string | number };
|
|
14
|
-
share?: { amount?: number };
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Get the split configuration for a hotspot.
|
|
20
|
-
*/
|
|
21
|
-
export const getSplit = publicProcedure
|
|
22
|
-
.route({
|
|
23
|
-
method: "GET",
|
|
24
|
-
path: "/hotspots/wallet/{walletAddress}/{hotspotPubkey}/split",
|
|
25
|
-
})
|
|
26
|
-
.input(GetSplitInputSchema)
|
|
27
|
-
.output(SplitResponseSchema)
|
|
28
|
-
.errors({
|
|
29
|
-
NOT_FOUND: { message: "Hotspot not found or does not have a split" },
|
|
30
|
-
})
|
|
31
|
-
.handler(async ({ input, errors }) => {
|
|
32
|
-
const { walletAddress, hotspotPubkey } = input;
|
|
33
|
-
|
|
34
|
-
await connectToDb();
|
|
35
|
-
|
|
36
|
-
// Resolve hotspot pubkey to asset ID
|
|
37
|
-
const assetId = await getAssetIdFromPubkey(hotspotPubkey);
|
|
38
|
-
if (!assetId) {
|
|
39
|
-
throw errors.NOT_FOUND({ message: "Hotspot not found" });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Look up the asset and ensure it has a mini fanout configured
|
|
43
|
-
const assetOwner = await AssetOwner.findOne({
|
|
44
|
-
where: { asset: assetId },
|
|
45
|
-
include: [
|
|
46
|
-
{
|
|
47
|
-
model: Recipient,
|
|
48
|
-
as: "recipient",
|
|
49
|
-
required: true,
|
|
50
|
-
include: [
|
|
51
|
-
{
|
|
52
|
-
model: MiniFanout,
|
|
53
|
-
as: "split",
|
|
54
|
-
required: true,
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (!assetOwner?.recipient?.split) {
|
|
62
|
-
throw errors.NOT_FOUND({ message: "Hotspot does not have a split" });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const shares = assetOwner.recipient.split.shares as MiniFanoutShare[];
|
|
66
|
-
|
|
67
|
-
const totalShares = shares.reduce(
|
|
68
|
-
(acc: number, share: MiniFanoutShare) =>
|
|
69
|
-
acc + (share.share?.share?.amount || 0),
|
|
70
|
-
0,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
walletAddress,
|
|
75
|
-
hotspotPubkey,
|
|
76
|
-
splitAddress: assetOwner.recipient.split.address,
|
|
77
|
-
shares: shares.map((share: MiniFanoutShare) => ({
|
|
78
|
-
wallet: share.wallet,
|
|
79
|
-
delegate: share.delegate,
|
|
80
|
-
fixed: share.share?.fixed?.amount
|
|
81
|
-
? Number(share.share.fixed.amount)
|
|
82
|
-
: 0,
|
|
83
|
-
shares: share.share?.share?.amount
|
|
84
|
-
? share.share.share.amount / totalShares
|
|
85
|
-
: 0,
|
|
86
|
-
})),
|
|
87
|
-
};
|
|
88
|
-
});
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { publicProcedure } from "../../../procedures";
|
|
2
|
-
import {
|
|
3
|
-
TransferHotspotInputSchema,
|
|
4
|
-
TransferHotspotOutputSchema,
|
|
5
|
-
} from "../schemas";
|
|
6
|
-
import { ORPCError } from "@orpc/server";
|
|
7
|
-
import { Connection, PublicKey } from "@solana/web3.js";
|
|
8
|
-
import { env } from "@/lib/env";
|
|
9
|
-
import {
|
|
10
|
-
generateTransactionTag,
|
|
11
|
-
TRANSACTION_TYPES,
|
|
12
|
-
} from "@/lib/utils/transaction-tags";
|
|
13
|
-
import {
|
|
14
|
-
populateMissingDraftInfo,
|
|
15
|
-
toVersionedTx,
|
|
16
|
-
withPriorityFees,
|
|
17
|
-
proofArgsAndAccounts,
|
|
18
|
-
type Asset,
|
|
19
|
-
} from "@helium/spl-utils";
|
|
20
|
-
import {
|
|
21
|
-
PROGRAM_ID as BUBBLEGUM_PROGRAM_ID,
|
|
22
|
-
createTransferInstruction,
|
|
23
|
-
} from "@metaplex-foundation/mpl-bubblegum";
|
|
24
|
-
import {
|
|
25
|
-
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
|
|
26
|
-
SPL_NOOP_PROGRAM_ID,
|
|
27
|
-
} from "@solana/spl-account-compression";
|
|
28
|
-
import { entityCreatorKey } from "@helium/helium-entity-manager-sdk";
|
|
29
|
-
import { daoKey } from "@helium/helium-sub-daos-sdk";
|
|
30
|
-
import { HNT_MINT } from "@helium/spl-utils";
|
|
31
|
-
import { getAssetIdFromPubkey } from "@/lib/utils/hotspot-helpers";
|
|
32
|
-
|
|
33
|
-
const DAO_KEY = daoKey(HNT_MINT)[0];
|
|
34
|
-
|
|
35
|
-
async function getBubblegumAuthorityPDA(
|
|
36
|
-
merkleRollPubKey: PublicKey,
|
|
37
|
-
): Promise<PublicKey> {
|
|
38
|
-
const [bubblegumAuthorityPDAKey] = await PublicKey.findProgramAddress(
|
|
39
|
-
[merkleRollPubKey.toBuffer()],
|
|
40
|
-
BUBBLEGUM_PROGRAM_ID,
|
|
41
|
-
);
|
|
42
|
-
return bubblegumAuthorityPDAKey;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function validateHeliumHotspot(asset: Asset): boolean {
|
|
46
|
-
const heliumEntityCreator = entityCreatorKey(DAO_KEY)[0].toBase58();
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
asset.creators?.some((creator) => {
|
|
50
|
-
const address =
|
|
51
|
-
typeof creator.address === "string"
|
|
52
|
-
? creator.address
|
|
53
|
-
: creator.address.toBase58();
|
|
54
|
-
return address === heliumEntityCreator && creator.verified;
|
|
55
|
-
}) || false
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Create a transaction to transfer a hotspot to a new owner.
|
|
61
|
-
*/
|
|
62
|
-
export const transferHotspot = publicProcedure
|
|
63
|
-
.route({
|
|
64
|
-
method: "POST",
|
|
65
|
-
path: "/hotspots/wallet/{walletAddress}/{hotspotPubkey}/transfer",
|
|
66
|
-
})
|
|
67
|
-
.input(TransferHotspotInputSchema)
|
|
68
|
-
.output(TransferHotspotOutputSchema)
|
|
69
|
-
.errors({
|
|
70
|
-
NOT_FOUND: { message: "Hotspot not found" },
|
|
71
|
-
BAD_REQUEST: { message: "Invalid request" },
|
|
72
|
-
FORBIDDEN: { message: "Wallet is not the owner of this hotspot" },
|
|
73
|
-
INVALID_HOTSPOT: { message: "Asset is not a valid Helium hotspot" },
|
|
74
|
-
})
|
|
75
|
-
.handler(async ({ input, errors }) => {
|
|
76
|
-
const { walletAddress, hotspotPubkey, recipient } = input;
|
|
77
|
-
|
|
78
|
-
// Resolve hotspot pubkey to asset ID
|
|
79
|
-
const assetId = await getAssetIdFromPubkey(hotspotPubkey);
|
|
80
|
-
if (!assetId) {
|
|
81
|
-
throw errors.NOT_FOUND({ message: "Hotspot not found" });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Validate public keys
|
|
85
|
-
let payerPubkey: PublicKey;
|
|
86
|
-
let recipientPubkey: PublicKey;
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
payerPubkey = new PublicKey(walletAddress);
|
|
90
|
-
recipientPubkey = new PublicKey(recipient);
|
|
91
|
-
} catch {
|
|
92
|
-
throw errors.BAD_REQUEST({ message: "Invalid public key format" });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const connection = new Connection(env.SOLANA_RPC_URL);
|
|
96
|
-
const assetEndpoint = env.ASSET_ENDPOINT || connection.rpcEndpoint;
|
|
97
|
-
const assetPubkey = new PublicKey(assetId);
|
|
98
|
-
|
|
99
|
-
const { asset, args, accounts, remainingAccounts } =
|
|
100
|
-
await proofArgsAndAccounts({
|
|
101
|
-
connection,
|
|
102
|
-
assetId: assetPubkey,
|
|
103
|
-
assetEndpoint,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (!asset) {
|
|
107
|
-
throw errors.NOT_FOUND({ message: "Asset not found" });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Validate asset is a Helium hotspot
|
|
111
|
-
if (!validateHeliumHotspot(asset)) {
|
|
112
|
-
throw errors.INVALID_HOTSPOT({
|
|
113
|
-
message: "Asset is not a valid Helium hotspot",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Validate ownership
|
|
118
|
-
const ownerAddress =
|
|
119
|
-
typeof asset.ownership.owner === "string"
|
|
120
|
-
? asset.ownership.owner
|
|
121
|
-
: asset.ownership.owner.toBase58();
|
|
122
|
-
|
|
123
|
-
if (ownerAddress !== walletAddress) {
|
|
124
|
-
throw errors.FORBIDDEN({
|
|
125
|
-
message: "Wallet is not the owner of this hotspot",
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const leafOwner =
|
|
130
|
-
typeof asset.ownership.owner === "string"
|
|
131
|
-
? new PublicKey(asset.ownership.owner)
|
|
132
|
-
: asset.ownership.owner;
|
|
133
|
-
|
|
134
|
-
const leafDelegate = asset.ownership.delegate
|
|
135
|
-
? typeof asset.ownership.delegate === "string"
|
|
136
|
-
? new PublicKey(asset.ownership.delegate)
|
|
137
|
-
: asset.ownership.delegate
|
|
138
|
-
: leafOwner;
|
|
139
|
-
|
|
140
|
-
const merkleTree = accounts.merkleTree;
|
|
141
|
-
const treeAuthority = await getBubblegumAuthorityPDA(merkleTree);
|
|
142
|
-
|
|
143
|
-
const transferInstruction = createTransferInstruction(
|
|
144
|
-
{
|
|
145
|
-
treeAuthority,
|
|
146
|
-
leafOwner,
|
|
147
|
-
leafDelegate,
|
|
148
|
-
newLeafOwner: recipientPubkey,
|
|
149
|
-
merkleTree,
|
|
150
|
-
logWrapper: SPL_NOOP_PROGRAM_ID,
|
|
151
|
-
compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
|
|
152
|
-
anchorRemainingAccounts: remainingAccounts,
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
...args,
|
|
156
|
-
nonce: args.index,
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
const instructionsWithFees = await withPriorityFees({
|
|
161
|
-
connection,
|
|
162
|
-
instructions: [transferInstruction],
|
|
163
|
-
feePayer: payerPubkey,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const draft = await populateMissingDraftInfo(connection, {
|
|
167
|
-
instructions: instructionsWithFees,
|
|
168
|
-
feePayer: payerPubkey,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const versionedTx = toVersionedTx(draft);
|
|
172
|
-
const serializedTransaction = Buffer.from(versionedTx.serialize()).toString(
|
|
173
|
-
"base64",
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const tag = generateTransactionTag({
|
|
177
|
-
type: TRANSACTION_TYPES.HOTSPOT_TRANSFER,
|
|
178
|
-
walletAddress,
|
|
179
|
-
assetId,
|
|
180
|
-
recipient,
|
|
181
|
-
timestamp: Date.now(),
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const hotspotName = asset.content?.metadata?.name || "Hotspot";
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
transactionData: {
|
|
188
|
-
transactions: [
|
|
189
|
-
{
|
|
190
|
-
serializedTransaction,
|
|
191
|
-
metadata: {
|
|
192
|
-
type: TRANSACTION_TYPES.HOTSPOT_TRANSFER,
|
|
193
|
-
description: `Transfer ${hotspotName} to ${recipient.slice(
|
|
194
|
-
0,
|
|
195
|
-
4,
|
|
196
|
-
)}...${recipient.slice(-4)}`,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
parallel: false,
|
|
201
|
-
tag,
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
});
|