@utxopia/sdk 0.1.0-alpha.1

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/package.json +108 -0
  4. package/packages/btc-client/src/esplora-client.ts +153 -0
  5. package/packages/btc-client/src/index.ts +3 -0
  6. package/packages/btc-client/src/op-return.ts +93 -0
  7. package/packages/btc-client/src/types.ts +112 -0
  8. package/packages/sdk/README.md +277 -0
  9. package/packages/sdk/dist/announcement-client.d.ts +64 -0
  10. package/packages/sdk/dist/announcement-client.js +337 -0
  11. package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
  12. package/packages/sdk/dist/auditor-ciphertext.js +135 -0
  13. package/packages/sdk/dist/auditor.d.ts +115 -0
  14. package/packages/sdk/dist/auditor.js +292 -0
  15. package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
  16. package/packages/sdk/dist/bitcoin/ika.js +74 -0
  17. package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
  18. package/packages/sdk/dist/bitcoin/index.js +1 -0
  19. package/packages/sdk/dist/bound-params.d.ts +96 -0
  20. package/packages/sdk/dist/bound-params.js +249 -0
  21. package/packages/sdk/dist/chadbuffer.d.ts +124 -0
  22. package/packages/sdk/dist/chadbuffer.js +405 -0
  23. package/packages/sdk/dist/claim-link.d.ts +30 -0
  24. package/packages/sdk/dist/claim-link.js +50 -0
  25. package/packages/sdk/dist/client.d.ts +233 -0
  26. package/packages/sdk/dist/client.js +416 -0
  27. package/packages/sdk/dist/commitment-tree.d.ts +268 -0
  28. package/packages/sdk/dist/commitment-tree.js +564 -0
  29. package/packages/sdk/dist/config.d.ts +177 -0
  30. package/packages/sdk/dist/config.js +426 -0
  31. package/packages/sdk/dist/core/esplora.d.ts +110 -0
  32. package/packages/sdk/dist/core/esplora.js +187 -0
  33. package/packages/sdk/dist/core/mempool.d.ts +60 -0
  34. package/packages/sdk/dist/core/mempool.js +107 -0
  35. package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
  36. package/packages/sdk/dist/crypto-babyjub.js +312 -0
  37. package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
  38. package/packages/sdk/dist/crypto-ed25519.js +235 -0
  39. package/packages/sdk/dist/crypto.d.ts +59 -0
  40. package/packages/sdk/dist/crypto.js +140 -0
  41. package/packages/sdk/dist/event-client.d.ts +64 -0
  42. package/packages/sdk/dist/event-client.js +165 -0
  43. package/packages/sdk/dist/events.d.ts +127 -0
  44. package/packages/sdk/dist/events.js +305 -0
  45. package/packages/sdk/dist/explorer.d.ts +86 -0
  46. package/packages/sdk/dist/explorer.js +197 -0
  47. package/packages/sdk/dist/index.d.ts +61 -0
  48. package/packages/sdk/dist/index.js +216 -0
  49. package/packages/sdk/dist/instructions.d.ts +1125 -0
  50. package/packages/sdk/dist/instructions.js +1760 -0
  51. package/packages/sdk/dist/keys.d.ts +467 -0
  52. package/packages/sdk/dist/keys.js +799 -0
  53. package/packages/sdk/dist/logger.d.ts +10 -0
  54. package/packages/sdk/dist/logger.js +40 -0
  55. package/packages/sdk/dist/magicblock.d.ts +90 -0
  56. package/packages/sdk/dist/magicblock.js +164 -0
  57. package/packages/sdk/dist/merkle.d.ts +82 -0
  58. package/packages/sdk/dist/merkle.js +141 -0
  59. package/packages/sdk/dist/note.d.ts +348 -0
  60. package/packages/sdk/dist/note.js +483 -0
  61. package/packages/sdk/dist/pda.d.ts +160 -0
  62. package/packages/sdk/dist/pda.js +384 -0
  63. package/packages/sdk/dist/pool-state.d.ts +100 -0
  64. package/packages/sdk/dist/pool-state.js +126 -0
  65. package/packages/sdk/dist/poseidon.d.ts +53 -0
  66. package/packages/sdk/dist/poseidon.js +136 -0
  67. package/packages/sdk/dist/prover/index.d.ts +10 -0
  68. package/packages/sdk/dist/prover/index.js +10 -0
  69. package/packages/sdk/dist/prover/mobile.d.ts +45 -0
  70. package/packages/sdk/dist/prover/mobile.js +227 -0
  71. package/packages/sdk/dist/prover/web.d.ts +136 -0
  72. package/packages/sdk/dist/prover/web.js +597 -0
  73. package/packages/sdk/dist/psbt.d.ts +73 -0
  74. package/packages/sdk/dist/psbt.js +202 -0
  75. package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
  76. package/packages/sdk/dist/selective-disclosure.js +172 -0
  77. package/packages/sdk/dist/sender-memo.d.ts +149 -0
  78. package/packages/sdk/dist/sender-memo.js +250 -0
  79. package/packages/sdk/dist/sns-resolver.d.ts +121 -0
  80. package/packages/sdk/dist/sns-resolver.js +229 -0
  81. package/packages/sdk/dist/solana/connection.d.ts +77 -0
  82. package/packages/sdk/dist/solana/connection.js +133 -0
  83. package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
  84. package/packages/sdk/dist/solana/priority-fee.js +137 -0
  85. package/packages/sdk/dist/stealth.d.ts +429 -0
  86. package/packages/sdk/dist/stealth.js +703 -0
  87. package/packages/sdk/dist/taproot.d.ts +172 -0
  88. package/packages/sdk/dist/taproot.js +494 -0
  89. package/packages/sdk/dist/token-registry.d.ts +96 -0
  90. package/packages/sdk/dist/token-registry.js +122 -0
  91. package/packages/sdk/dist/utils/encoding.d.ts +19 -0
  92. package/packages/sdk/dist/utils/encoding.js +29 -0
  93. package/packages/sdk/dist/vk-registry.d.ts +100 -0
  94. package/packages/sdk/dist/vk-registry.js +212 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * SNS Subdomain Resolver for Stealth Addresses
3
+ *
4
+ * Resolves `<name>.utxopia.sol` (or configured parent domain) to stealth
5
+ * address keys stored in the SNS name record data field.
6
+ *
7
+ * On-chain data layout (after 96-byte SNS header):
8
+ * version(1) + viewingPubKey(32) + mpk(32) = 65 bytes
9
+ *
10
+ * - viewingPubKey: Ed25519 public key (for X25519 ECDH)
11
+ * - mpk: Master Public Key = Poseidon(spendingPub.x, spendingPub.y, nullifyingKey)
12
+ *
13
+ * These two keys are all a sender needs to create a stealth deposit.
14
+ * spendingPubKey is NOT stored — senders never use it.
15
+ *
16
+ * @module sns-resolver
17
+ */
18
+ import { getConfig } from "./config";
19
+ import { sha256Hash } from "./crypto";
20
+ // ========== Constants ==========
21
+ /** SNS name record header size (parent:32 + owner:32 + class:32) */
22
+ const SNS_HEADER_SIZE = 96;
23
+ /** Stealth data size: version(1) + viewingPubKey(32) + mpk(32) = 65 bytes */
24
+ export const SNS_STEALTH_DATA_SIZE = 65;
25
+ /** SNS hash prefix used for PDA derivation */
26
+ const HASH_PREFIX = "SPL Name Service";
27
+ /**
28
+ * Bit-flags a recipient can opt into on their SNS subdomain. The byte sits
29
+ * at offset 65 of the stealth payload (i.e. byte 161 of the on-chain
30
+ * account, after the 96-byte SNS header). Absent → all bits 0.
31
+ */
32
+ export const SnsComplianceFlags = {
33
+ /**
34
+ * Recipient is "auditor-disclosable by default" — they've signalled to
35
+ * senders that they're OK receiving outgoing audit memos, and likely
36
+ * already share a `DelegatedViewKey` with a designated auditor
37
+ * out-of-band.
38
+ */
39
+ AUDITOR_DISCLOSABLE: 1 << 0,
40
+ };
41
+ /** Returns true if the recipient has set the AUDITOR_DISCLOSABLE bit. */
42
+ export function isAuditorDisclosable(addr) {
43
+ return (addr.complianceFlags & SnsComplianceFlags.AUDITOR_DISCLOSABLE) !== 0;
44
+ }
45
+ /**
46
+ * Total bytes of the stealth payload's compliance extension when both
47
+ * pieces are present:
48
+ * [byte 65] complianceFlags (u8)
49
+ * [bytes 66..97] auditorPubkey (32-byte Solana pubkey)
50
+ *
51
+ * Records may carry only the base 65-byte payload, the payload plus
52
+ * `complianceFlags`, or the payload plus `complianceFlags` and `auditorPubkey`.
53
+ */
54
+ export const SNS_COMPLIANCE_AUDITOR_OFFSET = 66;
55
+ export const SNS_COMPLIANCE_AUDITOR_BYTES = 32;
56
+ // ========== PDA Derivation ==========
57
+ /**
58
+ * Hash a name for SNS PDA derivation (SHA256 of HASH_PREFIX + name)
59
+ */
60
+ function hashSnsName(name) {
61
+ const input = HASH_PREFIX + name;
62
+ return sha256Hash(new TextEncoder().encode(input));
63
+ }
64
+ // ========== PDA Derivation (internal) ==========
65
+ /**
66
+ * Derive the SNS key for a parent domain (e.g., "utxopia.sol")
67
+ */
68
+ export async function deriveParentDomainKey(parentDomain) {
69
+ const { address, getProgramDerivedAddress, getAddressEncoder } = await import("@solana/kit");
70
+ const config = getConfig();
71
+ if (!config.snsRootDomain) {
72
+ throw new Error("SNS root domain not configured for this network");
73
+ }
74
+ const hashedParent = hashSnsName(parentDomain);
75
+ const encoder = getAddressEncoder();
76
+ const [pda] = await getProgramDerivedAddress({
77
+ seeds: [
78
+ hashedParent,
79
+ new Uint8Array(32), // no class
80
+ new Uint8Array(encoder.encode(address(config.snsRootDomain))),
81
+ ],
82
+ programAddress: address(config.snsNameServiceProgramId),
83
+ });
84
+ return pda;
85
+ }
86
+ /**
87
+ * Derive the SNS key for a subdomain (e.g., "alice" under "utxopia.sol")
88
+ *
89
+ * Seeds: [hash("SPL Name Service" + "\0" + name), zeros(32), parentKey]
90
+ */
91
+ async function deriveSubdomainKey(subdomain, parentKey) {
92
+ const { address, getProgramDerivedAddress, getAddressEncoder } = await import("@solana/kit");
93
+ const config = getConfig();
94
+ const hashedSub = hashSnsName("\0" + subdomain);
95
+ const encoder = getAddressEncoder();
96
+ const [pda] = await getProgramDerivedAddress({
97
+ seeds: [
98
+ hashedSub,
99
+ new Uint8Array(32), // no class
100
+ new Uint8Array(encoder.encode(address(parentKey))),
101
+ ],
102
+ programAddress: address(config.snsNameServiceProgramId),
103
+ });
104
+ return pda;
105
+ }
106
+ // ========== Parsing ==========
107
+ /**
108
+ * Parse stealth address data from an SNS name record.
109
+ *
110
+ * Supports the current format:
111
+ * - Base (65 bytes, version 2): version(1) + viewingPubKey(32) + mpk(32)
112
+ *
113
+ * Optional trailing byte (offset 65 of the stealth payload, byte 161 of the
114
+ * account) carries `complianceFlags: u8` — see {@link SnsComplianceFlags}.
115
+ * When absent, the parsed result has `complianceFlags = 0`.
116
+ *
117
+ * @param accountData - Raw account data (including 96-byte header)
118
+ * @returns Parsed stealth keys + compliance flags, or null if invalid
119
+ */
120
+ export function parseSnsStealthData(accountData) {
121
+ // Need at least header + 65 bytes of stealth data
122
+ if (accountData.length < SNS_HEADER_SIZE + SNS_STEALTH_DATA_SIZE) {
123
+ return null;
124
+ }
125
+ const data = accountData.slice(SNS_HEADER_SIZE);
126
+ const version = data[0];
127
+ const allZero = (buf) => buf.every((b) => b === 0);
128
+ if (version !== 2) {
129
+ return null;
130
+ }
131
+ const viewingPubKey = data.slice(1, 33);
132
+ const mpk = data.slice(33, 65);
133
+ if (allZero(viewingPubKey) || allZero(mpk)) {
134
+ return null;
135
+ }
136
+ const complianceFlags = data.length > SNS_STEALTH_DATA_SIZE ? data[SNS_STEALTH_DATA_SIZE] : 0;
137
+ let auditorPubkey;
138
+ if (data.length >= SNS_COMPLIANCE_AUDITOR_OFFSET + SNS_COMPLIANCE_AUDITOR_BYTES) {
139
+ const buf = data.slice(SNS_COMPLIANCE_AUDITOR_OFFSET, SNS_COMPLIANCE_AUDITOR_OFFSET + SNS_COMPLIANCE_AUDITOR_BYTES);
140
+ // All-zero pubkey means "no auditor set" — distinguishes from the
141
+ // case where a recipient flips the flag bit but skips the pubkey.
142
+ if (!allZero(buf)) {
143
+ auditorPubkey = new Uint8Array(buf);
144
+ }
145
+ }
146
+ return {
147
+ viewingPubKey: new Uint8Array(viewingPubKey),
148
+ mpk: new Uint8Array(mpk),
149
+ version,
150
+ complianceFlags,
151
+ auditorPubkey,
152
+ };
153
+ }
154
+ // ========== Resolution ==========
155
+ /**
156
+ * Resolve a name to a stealth address via SNS subdomain.
157
+ *
158
+ * Accepts multiple formats:
159
+ * - "alice" → resolves alice.<parentDomain>.sol
160
+ * - "alice.utxopia" → resolves alice.utxopia.sol
161
+ * - "alice.utxopia.sol" → resolves alice.utxopia.sol
162
+ *
163
+ * @param connection - RPC connection adapter
164
+ * @param name - Name to resolve
165
+ * @returns Stealth address or null if not found / no stealth data
166
+ */
167
+ export async function resolveSnsName(connection, name) {
168
+ const config = getConfig();
169
+ if (!config.snsNameServiceProgramId || !config.snsParentDomain) {
170
+ return null; // SNS not configured
171
+ }
172
+ // Normalize: strip .sol and parent domain suffix
173
+ const parentDomain = config.snsParentDomain;
174
+ let subdomain = name.trim().toLowerCase();
175
+ if (subdomain.endsWith(".sol")) {
176
+ subdomain = subdomain.slice(0, -4);
177
+ }
178
+ if (subdomain.endsWith("." + parentDomain)) {
179
+ subdomain = subdomain.slice(0, -(parentDomain.length + 1));
180
+ }
181
+ if (!subdomain || subdomain.includes(".")) {
182
+ return null; // Invalid: either empty or has extra dots
183
+ }
184
+ try {
185
+ // Derive parent domain key
186
+ const parentKey = await deriveParentDomainKey(parentDomain);
187
+ // Derive subdomain key
188
+ const subKey = await deriveSubdomainKey(subdomain, parentKey);
189
+ // Fetch account
190
+ const accountInfo = await connection.getAccountInfo(subKey);
191
+ if (!accountInfo) {
192
+ return null;
193
+ }
194
+ // Parse stealth data
195
+ const parsed = parseSnsStealthData(new Uint8Array(accountInfo.data));
196
+ if (!parsed) {
197
+ return null;
198
+ }
199
+ const fullDomain = `${subdomain}.${parentDomain}.sol`;
200
+ return {
201
+ name: subdomain,
202
+ fullDomain,
203
+ viewingPubKey: parsed.viewingPubKey,
204
+ mpk: parsed.mpk,
205
+ version: parsed.version,
206
+ complianceFlags: parsed.complianceFlags,
207
+ auditorPubkey: parsed.auditorPubkey,
208
+ };
209
+ }
210
+ catch (err) {
211
+ console.error(`Failed to resolve SNS name "${name}":`, err);
212
+ return null;
213
+ }
214
+ }
215
+ /**
216
+ * Resolve a stealth name via SNS subdomain.
217
+ *
218
+ * This is the unified resolver that the frontend should use.
219
+ * All names are resolved as SNS subdomains under the configured parent domain.
220
+ */
221
+ export async function resolveStealthName(connection, name) {
222
+ return resolveSnsName(connection, name);
223
+ }
224
+ /**
225
+ * Type guard to check if a resolved address is from SNS
226
+ */
227
+ export function isSnsStealthAddress(addr) {
228
+ return typeof addr === "object" && addr !== null && "fullDomain" in addr;
229
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Connection Adapter Factory
3
+ *
4
+ * Creates ConnectionAdapter instances for use with SDK functions.
5
+ * Supports both @solana/kit and @solana/web3.js connections.
6
+ */
7
+ import type { ConnectionAdapter } from "../stealth";
8
+ export interface RpcConfig {
9
+ /** RPC endpoint URL */
10
+ endpoint: string;
11
+ /** Commitment level */
12
+ commitment?: "processed" | "confirmed" | "finalized";
13
+ }
14
+ /**
15
+ * Minimal interface for @solana/web3.js Connection-like objects
16
+ */
17
+ export interface Web3Connection {
18
+ getAccountInfo(publicKey: {
19
+ toBase58(): string;
20
+ } | string, commitment?: string): Promise<{
21
+ data: Buffer | Uint8Array;
22
+ } | null>;
23
+ }
24
+ /**
25
+ * Minimal interface for @solana/kit Rpc-like objects
26
+ */
27
+ export interface KitRpc {
28
+ getAccountInfo(address: string, config?: {
29
+ encoding: string;
30
+ }): {
31
+ send(): Promise<{
32
+ value: {
33
+ data: string | string[];
34
+ } | null;
35
+ }>;
36
+ };
37
+ }
38
+ /**
39
+ * Create a ConnectionAdapter using fetch (works everywhere)
40
+ *
41
+ * This is the most portable option - works in browser, Node.js, and React Native.
42
+ *
43
+ * @param endpoint - Solana RPC endpoint URL
44
+ * @returns ConnectionAdapter instance
45
+ */
46
+ export declare function createFetchConnectionAdapter(endpoint: string): ConnectionAdapter;
47
+ /**
48
+ * Create a ConnectionAdapter from @solana/web3.js Connection
49
+ *
50
+ * Use this when you already have a Connection instance from wallet adapter.
51
+ *
52
+ * @param connection - @solana/web3.js Connection instance
53
+ * @returns ConnectionAdapter instance
54
+ */
55
+ export declare function createConnectionAdapterFromWeb3(connection: Web3Connection): ConnectionAdapter;
56
+ /**
57
+ * Create a ConnectionAdapter from @solana/kit Rpc
58
+ *
59
+ * Use this when using the modern @solana/kit library.
60
+ *
61
+ * @param rpc - @solana/kit Rpc instance
62
+ * @returns ConnectionAdapter instance
63
+ */
64
+ export declare function createConnectionAdapterFromKit(rpc: KitRpc): ConnectionAdapter;
65
+ /**
66
+ * Get or create a cached ConnectionAdapter
67
+ *
68
+ * Caches the adapter for the given endpoint to avoid creating multiple instances.
69
+ *
70
+ * @param endpoint - Solana RPC endpoint URL
71
+ * @returns ConnectionAdapter instance
72
+ */
73
+ export declare function getConnectionAdapter(endpoint: string): ConnectionAdapter;
74
+ /**
75
+ * Clear the cached ConnectionAdapter
76
+ */
77
+ export declare function clearConnectionAdapterCache(): void;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Connection Adapter Factory
3
+ *
4
+ * Creates ConnectionAdapter instances for use with SDK functions.
5
+ * Supports both @solana/kit and @solana/web3.js connections.
6
+ */
7
+ import { base64ToBinaryString } from "../utils/encoding";
8
+ // =============================================================================
9
+ // Connection Adapter Factory
10
+ // =============================================================================
11
+ /**
12
+ * Create a ConnectionAdapter using fetch (works everywhere)
13
+ *
14
+ * This is the most portable option - works in browser, Node.js, and React Native.
15
+ *
16
+ * @param endpoint - Solana RPC endpoint URL
17
+ * @returns ConnectionAdapter instance
18
+ */
19
+ export function createFetchConnectionAdapter(endpoint) {
20
+ return {
21
+ getAccountInfo: async (address) => {
22
+ const response = await fetch(endpoint, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify({
26
+ jsonrpc: "2.0",
27
+ id: 1,
28
+ method: "getAccountInfo",
29
+ params: [address, { encoding: "base64" }],
30
+ }),
31
+ });
32
+ const result = await response.json();
33
+ if (!result.result?.value) {
34
+ return null;
35
+ }
36
+ // Decode base64 data
37
+ const base64Data = Array.isArray(result.result.value.data)
38
+ ? result.result.value.data[0]
39
+ : result.result.value.data;
40
+ if (!base64Data) {
41
+ return { data: new Uint8Array() };
42
+ }
43
+ // Decode base64 (works in browser and Node.js)
44
+ const binaryString = base64ToBinaryString(base64Data);
45
+ const bytes = new Uint8Array(binaryString.length);
46
+ for (let i = 0; i < binaryString.length; i++) {
47
+ bytes[i] = binaryString.charCodeAt(i);
48
+ }
49
+ return { data: bytes };
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * Create a ConnectionAdapter from @solana/web3.js Connection
55
+ *
56
+ * Use this when you already have a Connection instance from wallet adapter.
57
+ *
58
+ * @param connection - @solana/web3.js Connection instance
59
+ * @returns ConnectionAdapter instance
60
+ */
61
+ export function createConnectionAdapterFromWeb3(connection) {
62
+ return {
63
+ getAccountInfo: async (address) => {
64
+ // Handle both string and PublicKey-like objects
65
+ const info = await connection.getAccountInfo(address);
66
+ if (!info)
67
+ return null;
68
+ return { data: new Uint8Array(info.data) };
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * Create a ConnectionAdapter from @solana/kit Rpc
74
+ *
75
+ * Use this when using the modern @solana/kit library.
76
+ *
77
+ * @param rpc - @solana/kit Rpc instance
78
+ * @returns ConnectionAdapter instance
79
+ */
80
+ export function createConnectionAdapterFromKit(rpc) {
81
+ return {
82
+ getAccountInfo: async (address) => {
83
+ const result = await rpc
84
+ .getAccountInfo(address, { encoding: "base64" })
85
+ .send();
86
+ if (!result.value) {
87
+ return null;
88
+ }
89
+ // Decode base64 data
90
+ const base64Data = typeof result.value.data === "string"
91
+ ? result.value.data
92
+ : result.value.data[0];
93
+ if (!base64Data) {
94
+ return { data: new Uint8Array() };
95
+ }
96
+ // Decode base64
97
+ const binaryString = base64ToBinaryString(base64Data);
98
+ const bytes = new Uint8Array(binaryString.length);
99
+ for (let i = 0; i < binaryString.length; i++) {
100
+ bytes[i] = binaryString.charCodeAt(i);
101
+ }
102
+ return { data: bytes };
103
+ },
104
+ };
105
+ }
106
+ // =============================================================================
107
+ // Cached Connection Adapter
108
+ // =============================================================================
109
+ let cachedAdapter = null;
110
+ let cachedEndpoint = null;
111
+ /**
112
+ * Get or create a cached ConnectionAdapter
113
+ *
114
+ * Caches the adapter for the given endpoint to avoid creating multiple instances.
115
+ *
116
+ * @param endpoint - Solana RPC endpoint URL
117
+ * @returns ConnectionAdapter instance
118
+ */
119
+ export function getConnectionAdapter(endpoint) {
120
+ if (cachedAdapter && cachedEndpoint === endpoint) {
121
+ return cachedAdapter;
122
+ }
123
+ cachedAdapter = createFetchConnectionAdapter(endpoint);
124
+ cachedEndpoint = endpoint;
125
+ return cachedAdapter;
126
+ }
127
+ /**
128
+ * Clear the cached ConnectionAdapter
129
+ */
130
+ export function clearConnectionAdapterCache() {
131
+ cachedAdapter = null;
132
+ cachedEndpoint = null;
133
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Priority Fee Estimation
3
+ *
4
+ * Utilities for estimating Solana transaction priority fees.
5
+ * Supports Helius API for accurate fee estimation.
6
+ */
7
+ export interface PriorityFeeConfig {
8
+ /** Helius API key (optional, falls back to default fees without it) */
9
+ heliusApiKey?: string;
10
+ /** RPC endpoint (defaults to Helius if API key provided) */
11
+ rpcEndpoint?: string;
12
+ /** Default compute unit limit */
13
+ defaultComputeUnits?: number;
14
+ /** Default priority fee in microLamports (fallback) */
15
+ defaultPriorityFee?: number;
16
+ }
17
+ export interface PriorityFeeEstimate {
18
+ /** Recommended priority fee in microLamports */
19
+ priorityFee: number;
20
+ /** Compute unit limit to set */
21
+ computeUnits: number;
22
+ }
23
+ export interface PriorityFeeInstructions {
24
+ /** SetComputeUnitLimit instruction data */
25
+ setComputeUnitLimit: {
26
+ discriminator: number;
27
+ units: number;
28
+ };
29
+ /** SetComputeUnitPrice instruction data (null if no priority fee) */
30
+ setComputeUnitPrice: {
31
+ discriminator: number;
32
+ microLamports: bigint;
33
+ } | null;
34
+ }
35
+ /** Default compute unit limit for UTXOpia transactions */
36
+ export declare const DEFAULT_COMPUTE_UNITS = 200000;
37
+ /** Default priority fee in microLamports when estimation fails */
38
+ export declare const DEFAULT_PRIORITY_FEE = 1000;
39
+ /** ComputeBudgetProgram discriminators */
40
+ export declare const COMPUTE_BUDGET_DISCRIMINATORS: {
41
+ readonly SET_COMPUTE_UNIT_LIMIT: 2;
42
+ readonly SET_COMPUTE_UNIT_PRICE: 3;
43
+ };
44
+ /**
45
+ * Estimate priority fee using Helius API
46
+ *
47
+ * @param accountKeys - Array of account public key strings involved in the transaction
48
+ * @param config - Configuration options
49
+ * @returns Priority fee estimate
50
+ */
51
+ export declare function estimatePriorityFee(accountKeys: string[], config?: PriorityFeeConfig): Promise<PriorityFeeEstimate>;
52
+ /**
53
+ * Build priority fee instruction data
54
+ *
55
+ * Returns raw instruction data for ComputeBudgetProgram instructions.
56
+ * Use this when building transactions manually.
57
+ *
58
+ * @param accountKeys - Array of account public key strings
59
+ * @param config - Configuration options
60
+ * @returns Instruction data for compute budget instructions
61
+ */
62
+ export declare function buildPriorityFeeInstructionData(accountKeys: string[], config?: PriorityFeeConfig): Promise<PriorityFeeInstructions>;
63
+ /**
64
+ * Encode SetComputeUnitLimit instruction
65
+ */
66
+ export declare function encodeSetComputeUnitLimit(units: number): Uint8Array;
67
+ /**
68
+ * Encode SetComputeUnitPrice instruction
69
+ */
70
+ export declare function encodeSetComputeUnitPrice(microLamports: bigint): Uint8Array;
71
+ /**
72
+ * Get RPC URL with Helius API key
73
+ *
74
+ * @param network - "devnet" or "mainnet"
75
+ * @param heliusApiKey - Optional Helius API key
76
+ * @returns RPC URL
77
+ */
78
+ export declare function getHeliusRpcUrl(network: "devnet" | "mainnet", heliusApiKey?: string): string;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Priority Fee Estimation
3
+ *
4
+ * Utilities for estimating Solana transaction priority fees.
5
+ * Supports Helius API for accurate fee estimation.
6
+ */
7
+ // =============================================================================
8
+ // Constants
9
+ // =============================================================================
10
+ /** Default compute unit limit for UTXOpia transactions */
11
+ export const DEFAULT_COMPUTE_UNITS = 200000;
12
+ /** Default priority fee in microLamports when estimation fails */
13
+ export const DEFAULT_PRIORITY_FEE = 1000;
14
+ /** ComputeBudgetProgram discriminators */
15
+ export const COMPUTE_BUDGET_DISCRIMINATORS = {
16
+ SET_COMPUTE_UNIT_LIMIT: 2,
17
+ SET_COMPUTE_UNIT_PRICE: 3,
18
+ };
19
+ // =============================================================================
20
+ // Priority Fee Estimation
21
+ // =============================================================================
22
+ /**
23
+ * Estimate priority fee using Helius API
24
+ *
25
+ * @param accountKeys - Array of account public key strings involved in the transaction
26
+ * @param config - Configuration options
27
+ * @returns Priority fee estimate
28
+ */
29
+ export async function estimatePriorityFee(accountKeys, config = {}) {
30
+ const { heliusApiKey, rpcEndpoint, defaultComputeUnits = DEFAULT_COMPUTE_UNITS, defaultPriorityFee = DEFAULT_PRIORITY_FEE, } = config;
31
+ // Determine endpoint
32
+ const endpoint = rpcEndpoint || (heliusApiKey
33
+ ? `https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`
34
+ : null);
35
+ if (!endpoint) {
36
+ // No API available, return defaults
37
+ return {
38
+ priorityFee: defaultPriorityFee,
39
+ computeUnits: defaultComputeUnits,
40
+ };
41
+ }
42
+ try {
43
+ const response = await fetch(endpoint, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({
47
+ jsonrpc: "2.0",
48
+ id: "priority-fee",
49
+ method: "getPriorityFeeEstimate",
50
+ params: [{
51
+ accountKeys,
52
+ options: { recommended: true },
53
+ }],
54
+ }),
55
+ });
56
+ const data = await response.json();
57
+ const priorityFee = data?.result?.priorityFeeEstimate || defaultPriorityFee;
58
+ return {
59
+ priorityFee: Math.round(priorityFee),
60
+ computeUnits: defaultComputeUnits,
61
+ };
62
+ }
63
+ catch (error) {
64
+ console.warn("Failed to get priority fee estimate:", error);
65
+ return {
66
+ priorityFee: defaultPriorityFee,
67
+ computeUnits: defaultComputeUnits,
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Build priority fee instruction data
73
+ *
74
+ * Returns raw instruction data for ComputeBudgetProgram instructions.
75
+ * Use this when building transactions manually.
76
+ *
77
+ * @param accountKeys - Array of account public key strings
78
+ * @param config - Configuration options
79
+ * @returns Instruction data for compute budget instructions
80
+ */
81
+ export async function buildPriorityFeeInstructionData(accountKeys, config = {}) {
82
+ const estimate = await estimatePriorityFee(accountKeys, config);
83
+ return {
84
+ setComputeUnitLimit: {
85
+ discriminator: COMPUTE_BUDGET_DISCRIMINATORS.SET_COMPUTE_UNIT_LIMIT,
86
+ units: estimate.computeUnits,
87
+ },
88
+ setComputeUnitPrice: estimate.priorityFee > 0
89
+ ? {
90
+ discriminator: COMPUTE_BUDGET_DISCRIMINATORS.SET_COMPUTE_UNIT_PRICE,
91
+ microLamports: BigInt(estimate.priorityFee),
92
+ }
93
+ : null,
94
+ };
95
+ }
96
+ /**
97
+ * Encode SetComputeUnitLimit instruction
98
+ */
99
+ export function encodeSetComputeUnitLimit(units) {
100
+ const data = new Uint8Array(5);
101
+ data[0] = COMPUTE_BUDGET_DISCRIMINATORS.SET_COMPUTE_UNIT_LIMIT;
102
+ // Little-endian u32
103
+ data[1] = units & 0xff;
104
+ data[2] = (units >> 8) & 0xff;
105
+ data[3] = (units >> 16) & 0xff;
106
+ data[4] = (units >> 24) & 0xff;
107
+ return data;
108
+ }
109
+ /**
110
+ * Encode SetComputeUnitPrice instruction
111
+ */
112
+ export function encodeSetComputeUnitPrice(microLamports) {
113
+ const data = new Uint8Array(9);
114
+ data[0] = COMPUTE_BUDGET_DISCRIMINATORS.SET_COMPUTE_UNIT_PRICE;
115
+ // Little-endian u64
116
+ for (let i = 0; i < 8; i++) {
117
+ data[1 + i] = Number((microLamports >> BigInt(i * 8)) & 0xffn);
118
+ }
119
+ return data;
120
+ }
121
+ /**
122
+ * Get RPC URL with Helius API key
123
+ *
124
+ * @param network - "devnet" or "mainnet"
125
+ * @param heliusApiKey - Optional Helius API key
126
+ * @returns RPC URL
127
+ */
128
+ export function getHeliusRpcUrl(network, heliusApiKey) {
129
+ if (heliusApiKey) {
130
+ return network === "mainnet"
131
+ ? `https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`
132
+ : `https://devnet.helius-rpc.com/?api-key=${heliusApiKey}`;
133
+ }
134
+ return network === "mainnet"
135
+ ? "https://api.mainnet-beta.solana.com"
136
+ : "https://api.devnet.solana.com";
137
+ }