@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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/package.json +108 -0
- package/packages/btc-client/src/esplora-client.ts +153 -0
- package/packages/btc-client/src/index.ts +3 -0
- package/packages/btc-client/src/op-return.ts +93 -0
- package/packages/btc-client/src/types.ts +112 -0
- package/packages/sdk/README.md +277 -0
- package/packages/sdk/dist/announcement-client.d.ts +64 -0
- package/packages/sdk/dist/announcement-client.js +337 -0
- package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
- package/packages/sdk/dist/auditor-ciphertext.js +135 -0
- package/packages/sdk/dist/auditor.d.ts +115 -0
- package/packages/sdk/dist/auditor.js +292 -0
- package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
- package/packages/sdk/dist/bitcoin/ika.js +74 -0
- package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
- package/packages/sdk/dist/bitcoin/index.js +1 -0
- package/packages/sdk/dist/bound-params.d.ts +96 -0
- package/packages/sdk/dist/bound-params.js +249 -0
- package/packages/sdk/dist/chadbuffer.d.ts +124 -0
- package/packages/sdk/dist/chadbuffer.js +405 -0
- package/packages/sdk/dist/claim-link.d.ts +30 -0
- package/packages/sdk/dist/claim-link.js +50 -0
- package/packages/sdk/dist/client.d.ts +233 -0
- package/packages/sdk/dist/client.js +416 -0
- package/packages/sdk/dist/commitment-tree.d.ts +268 -0
- package/packages/sdk/dist/commitment-tree.js +564 -0
- package/packages/sdk/dist/config.d.ts +177 -0
- package/packages/sdk/dist/config.js +426 -0
- package/packages/sdk/dist/core/esplora.d.ts +110 -0
- package/packages/sdk/dist/core/esplora.js +187 -0
- package/packages/sdk/dist/core/mempool.d.ts +60 -0
- package/packages/sdk/dist/core/mempool.js +107 -0
- package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
- package/packages/sdk/dist/crypto-babyjub.js +312 -0
- package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
- package/packages/sdk/dist/crypto-ed25519.js +235 -0
- package/packages/sdk/dist/crypto.d.ts +59 -0
- package/packages/sdk/dist/crypto.js +140 -0
- package/packages/sdk/dist/event-client.d.ts +64 -0
- package/packages/sdk/dist/event-client.js +165 -0
- package/packages/sdk/dist/events.d.ts +127 -0
- package/packages/sdk/dist/events.js +305 -0
- package/packages/sdk/dist/explorer.d.ts +86 -0
- package/packages/sdk/dist/explorer.js +197 -0
- package/packages/sdk/dist/index.d.ts +61 -0
- package/packages/sdk/dist/index.js +216 -0
- package/packages/sdk/dist/instructions.d.ts +1125 -0
- package/packages/sdk/dist/instructions.js +1760 -0
- package/packages/sdk/dist/keys.d.ts +467 -0
- package/packages/sdk/dist/keys.js +799 -0
- package/packages/sdk/dist/logger.d.ts +10 -0
- package/packages/sdk/dist/logger.js +40 -0
- package/packages/sdk/dist/magicblock.d.ts +90 -0
- package/packages/sdk/dist/magicblock.js +164 -0
- package/packages/sdk/dist/merkle.d.ts +82 -0
- package/packages/sdk/dist/merkle.js +141 -0
- package/packages/sdk/dist/note.d.ts +348 -0
- package/packages/sdk/dist/note.js +483 -0
- package/packages/sdk/dist/pda.d.ts +160 -0
- package/packages/sdk/dist/pda.js +384 -0
- package/packages/sdk/dist/pool-state.d.ts +100 -0
- package/packages/sdk/dist/pool-state.js +126 -0
- package/packages/sdk/dist/poseidon.d.ts +53 -0
- package/packages/sdk/dist/poseidon.js +136 -0
- package/packages/sdk/dist/prover/index.d.ts +10 -0
- package/packages/sdk/dist/prover/index.js +10 -0
- package/packages/sdk/dist/prover/mobile.d.ts +45 -0
- package/packages/sdk/dist/prover/mobile.js +227 -0
- package/packages/sdk/dist/prover/web.d.ts +136 -0
- package/packages/sdk/dist/prover/web.js +597 -0
- package/packages/sdk/dist/psbt.d.ts +73 -0
- package/packages/sdk/dist/psbt.js +202 -0
- package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
- package/packages/sdk/dist/selective-disclosure.js +172 -0
- package/packages/sdk/dist/sender-memo.d.ts +149 -0
- package/packages/sdk/dist/sender-memo.js +250 -0
- package/packages/sdk/dist/sns-resolver.d.ts +121 -0
- package/packages/sdk/dist/sns-resolver.js +229 -0
- package/packages/sdk/dist/solana/connection.d.ts +77 -0
- package/packages/sdk/dist/solana/connection.js +133 -0
- package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
- package/packages/sdk/dist/solana/priority-fee.js +137 -0
- package/packages/sdk/dist/stealth.d.ts +429 -0
- package/packages/sdk/dist/stealth.js +703 -0
- package/packages/sdk/dist/taproot.d.ts +172 -0
- package/packages/sdk/dist/taproot.js +494 -0
- package/packages/sdk/dist/token-registry.d.ts +96 -0
- package/packages/sdk/dist/token-registry.js +122 -0
- package/packages/sdk/dist/utils/encoding.d.ts +19 -0
- package/packages/sdk/dist/utils/encoding.js +29 -0
- package/packages/sdk/dist/vk-registry.d.ts +100 -0
- 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
|
+
}
|