@oobe-protocol-labs/synapse-sap-sdk 0.4.2 → 0.6.0
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/dist/cjs/constants/index.js +4 -1
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/constants/network.js +81 -0
- package/dist/cjs/constants/network.js.map +1 -0
- package/dist/cjs/index.js +36 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/registries/x402.js +8 -3
- package/dist/cjs/registries/x402.js.map +1 -1
- package/dist/cjs/types/endpoint.js +15 -0
- package/dist/cjs/types/endpoint.js.map +1 -0
- package/dist/cjs/utils/endpoint-validator.js +232 -0
- package/dist/cjs/utils/endpoint-validator.js.map +1 -0
- package/dist/cjs/utils/index.js +30 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/network-normalizer.js +236 -0
- package/dist/cjs/utils/network-normalizer.js.map +1 -0
- package/dist/cjs/utils/rpc-strategy.js +239 -0
- package/dist/cjs/utils/rpc-strategy.js.map +1 -0
- package/dist/cjs/utils/schemas.js +331 -0
- package/dist/cjs/utils/schemas.js.map +1 -0
- package/dist/esm/constants/index.js +2 -0
- package/dist/esm/constants/index.js.map +1 -1
- package/dist/esm/constants/network.js +78 -0
- package/dist/esm/constants/network.js.map +1 -0
- package/dist/esm/index.js +9 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/registries/x402.js +8 -3
- package/dist/esm/registries/x402.js.map +1 -1
- package/dist/esm/types/endpoint.js +14 -0
- package/dist/esm/types/endpoint.js.map +1 -0
- package/dist/esm/utils/endpoint-validator.js +226 -0
- package/dist/esm/utils/endpoint-validator.js.map +1 -0
- package/dist/esm/utils/index.js +5 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/network-normalizer.js +229 -0
- package/dist/esm/utils/network-normalizer.js.map +1 -0
- package/dist/esm/utils/rpc-strategy.js +231 -0
- package/dist/esm/utils/rpc-strategy.js.map +1 -0
- package/dist/esm/utils/schemas.js +320 -0
- package/dist/esm/utils/schemas.js.map +1 -0
- package/dist/types/constants/index.d.ts +2 -0
- package/dist/types/constants/index.d.ts.map +1 -1
- package/dist/types/constants/network.d.ts +81 -0
- package/dist/types/constants/network.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/plugin/schemas.d.ts +6 -6
- package/dist/types/registries/x402.d.ts +35 -2
- package/dist/types/registries/x402.d.ts.map +1 -1
- package/dist/types/types/endpoint.d.ts +161 -0
- package/dist/types/types/endpoint.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/utils/endpoint-validator.d.ts +110 -0
- package/dist/types/utils/endpoint-validator.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/network-normalizer.d.ts +120 -0
- package/dist/types/utils/network-normalizer.d.ts.map +1 -0
- package/dist/types/utils/rpc-strategy.d.ts +172 -0
- package/dist/types/utils/rpc-strategy.d.ts.map +1 -0
- package/dist/types/utils/schemas.d.ts +351 -0
- package/dist/types/utils/schemas.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/constants/index.ts +4 -0
- package/src/constants/network.ts +89 -0
- package/src/index.ts +50 -0
- package/src/registries/x402.ts +43 -4
- package/src/types/endpoint.ts +181 -0
- package/src/types/index.ts +9 -0
- package/src/utils/endpoint-validator.ts +300 -0
- package/src/utils/index.ts +39 -0
- package/src/utils/network-normalizer.ts +240 -0
- package/src/utils/rpc-strategy.ts +322 -0
- package/src/utils/schemas.ts +359 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module utils/network-normalizer
|
|
4
|
+
* @description Network identifier normalization for x402 payment headers.
|
|
5
|
+
*
|
|
6
|
+
* Solves the canonical-string mismatch between SAP clients and servers:
|
|
7
|
+
* some providers accept `solana:mainnet-beta` while others require
|
|
8
|
+
* the genesis-hash form `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`.
|
|
9
|
+
*
|
|
10
|
+
* This module provides a single source of truth for normalizing
|
|
11
|
+
* network identifiers so that both clients and agents canonicalize
|
|
12
|
+
* before comparing. Catches the Kamiyo "sap network mismatch" error
|
|
13
|
+
* at the SDK level.
|
|
14
|
+
*
|
|
15
|
+
* @category Utils
|
|
16
|
+
* @since v0.6.0
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.normalizeNetworkId = normalizeNetworkId;
|
|
20
|
+
exports.isNetworkEquivalent = isNetworkEquivalent;
|
|
21
|
+
exports.getNetworkGenesisHash = getNetworkGenesisHash;
|
|
22
|
+
exports.getNetworkClusterName = getNetworkClusterName;
|
|
23
|
+
exports.isKnownNetwork = isKnownNetwork;
|
|
24
|
+
const network_1 = require("../constants/network");
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
26
|
+
// Canonical Mapping
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
28
|
+
/**
|
|
29
|
+
* Bidirectional alias map: for every known network string, stores the
|
|
30
|
+
* canonical SapNetworkId it resolves to, plus all known aliases.
|
|
31
|
+
*/
|
|
32
|
+
const NETWORK_ALIASES = new Map([
|
|
33
|
+
// ── Mainnet ───────────────────────────────
|
|
34
|
+
// Canonical: genesis-hash form
|
|
35
|
+
["solana:mainnet-beta", network_1.SapNetwork.SOLANA_MAINNET],
|
|
36
|
+
["solana:mainnet", network_1.SapNetwork.SOLANA_MAINNET],
|
|
37
|
+
["mainnet-beta", network_1.SapNetwork.SOLANA_MAINNET],
|
|
38
|
+
["mainnet", network_1.SapNetwork.SOLANA_MAINNET],
|
|
39
|
+
["solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", network_1.SapNetwork.SOLANA_MAINNET_GENESIS],
|
|
40
|
+
["5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", network_1.SapNetwork.SOLANA_MAINNET_GENESIS],
|
|
41
|
+
// ── Devnet ────────────────────────────────
|
|
42
|
+
["solana:devnet", network_1.SapNetwork.SOLANA_DEVNET_NAMED],
|
|
43
|
+
["devnet", network_1.SapNetwork.SOLANA_DEVNET_NAMED],
|
|
44
|
+
["solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", network_1.SapNetwork.SOLANA_DEVNET],
|
|
45
|
+
["EtWTRABZaYq6iMfeYKouRu166VU2xqa1", network_1.SapNetwork.SOLANA_DEVNET],
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Mainnet equivalence set: all strings that refer to Solana mainnet-beta,
|
|
49
|
+
* regardless of format.
|
|
50
|
+
*/
|
|
51
|
+
const MAINNET_EQUIVALENTS = new Set([
|
|
52
|
+
network_1.SapNetwork.SOLANA_MAINNET,
|
|
53
|
+
network_1.SapNetwork.SOLANA_MAINNET_GENESIS,
|
|
54
|
+
"solana:mainnet",
|
|
55
|
+
"mainnet-beta",
|
|
56
|
+
"mainnet",
|
|
57
|
+
"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Devnet equivalence set.
|
|
61
|
+
*/
|
|
62
|
+
const DEVNET_EQUIVALENTS = new Set([
|
|
63
|
+
network_1.SapNetwork.SOLANA_DEVNET,
|
|
64
|
+
network_1.SapNetwork.SOLANA_DEVNET_NAMED,
|
|
65
|
+
"devnet",
|
|
66
|
+
"EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
67
|
+
]);
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
69
|
+
// Public API
|
|
70
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
71
|
+
/**
|
|
72
|
+
* @name normalizeNetworkId
|
|
73
|
+
* @description Normalize a raw network identifier string to its canonical
|
|
74
|
+
* {@link SapNetworkId} form.
|
|
75
|
+
*
|
|
76
|
+
* Handles:
|
|
77
|
+
* - Case-insensitive matching
|
|
78
|
+
* - Stripping whitespace
|
|
79
|
+
* - Resolving genesis-hash vs. cluster-name aliases
|
|
80
|
+
* - Unknown strings are returned as-is (passthrough)
|
|
81
|
+
*
|
|
82
|
+
* @param raw - Raw network identifier string from headers, env vars, or config.
|
|
83
|
+
* @returns The canonical {@link SapNetworkId}, or the trimmed input if unknown.
|
|
84
|
+
*
|
|
85
|
+
* @category Utils
|
|
86
|
+
* @since v0.6.0
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* import { normalizeNetworkId } from "@synapse-sap/sdk";
|
|
91
|
+
*
|
|
92
|
+
* normalizeNetworkId("solana:mainnet-beta");
|
|
93
|
+
* // → "solana:mainnet-beta"
|
|
94
|
+
*
|
|
95
|
+
* normalizeNetworkId("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
|
|
96
|
+
* // → "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
|
97
|
+
*
|
|
98
|
+
* normalizeNetworkId(" MAINNET ");
|
|
99
|
+
* // → "solana:mainnet-beta"
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
function normalizeNetworkId(raw) {
|
|
103
|
+
const trimmed = raw.trim();
|
|
104
|
+
// Exact match first
|
|
105
|
+
const exact = NETWORK_ALIASES.get(trimmed);
|
|
106
|
+
if (exact)
|
|
107
|
+
return exact;
|
|
108
|
+
// Case-insensitive lookup
|
|
109
|
+
const lower = trimmed.toLowerCase();
|
|
110
|
+
for (const [alias, canonical] of NETWORK_ALIASES) {
|
|
111
|
+
if (alias.toLowerCase() === lower)
|
|
112
|
+
return canonical;
|
|
113
|
+
}
|
|
114
|
+
// Passthrough for unknown networks (custom chains, etc.)
|
|
115
|
+
return trimmed;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* @name isNetworkEquivalent
|
|
119
|
+
* @description Check if two network identifier strings refer to the same network,
|
|
120
|
+
* even if they use different formats (cluster-name vs. genesis-hash).
|
|
121
|
+
*
|
|
122
|
+
* This is the key function that prevents the Kamiyo "sap network mismatch"
|
|
123
|
+
* error — instead of comparing strings literally, we compare their
|
|
124
|
+
* canonical equivalence class.
|
|
125
|
+
*
|
|
126
|
+
* @param a - First network identifier.
|
|
127
|
+
* @param b - Second network identifier.
|
|
128
|
+
* @returns `true` if both identifiers refer to the same Solana network.
|
|
129
|
+
*
|
|
130
|
+
* @category Utils
|
|
131
|
+
* @since v0.6.0
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* import { isNetworkEquivalent } from "@synapse-sap/sdk";
|
|
136
|
+
*
|
|
137
|
+
* isNetworkEquivalent("solana:mainnet-beta", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
|
|
138
|
+
* // → true
|
|
139
|
+
*
|
|
140
|
+
* isNetworkEquivalent("solana:devnet", "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1");
|
|
141
|
+
* // → true
|
|
142
|
+
*
|
|
143
|
+
* isNetworkEquivalent("solana:mainnet-beta", "solana:devnet");
|
|
144
|
+
* // → false
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
function isNetworkEquivalent(a, b) {
|
|
148
|
+
const normA = a.trim();
|
|
149
|
+
const normB = b.trim();
|
|
150
|
+
// Fast path: identical strings
|
|
151
|
+
if (normA === normB)
|
|
152
|
+
return true;
|
|
153
|
+
// Check if both are in the same equivalence set
|
|
154
|
+
if (MAINNET_EQUIVALENTS.has(normA) && MAINNET_EQUIVALENTS.has(normB))
|
|
155
|
+
return true;
|
|
156
|
+
if (DEVNET_EQUIVALENTS.has(normA) && DEVNET_EQUIVALENTS.has(normB))
|
|
157
|
+
return true;
|
|
158
|
+
// Case-insensitive match
|
|
159
|
+
const lowerA = normA.toLowerCase();
|
|
160
|
+
const lowerB = normB.toLowerCase();
|
|
161
|
+
if (lowerA === lowerB)
|
|
162
|
+
return true;
|
|
163
|
+
// Resolve both to canonical and compare
|
|
164
|
+
const canonA = normalizeNetworkId(normA);
|
|
165
|
+
const canonB = normalizeNetworkId(normB);
|
|
166
|
+
if (canonA === canonB)
|
|
167
|
+
return true;
|
|
168
|
+
// Final check: both resolve to the same equivalence set
|
|
169
|
+
if (MAINNET_EQUIVALENTS.has(canonA) && MAINNET_EQUIVALENTS.has(canonB))
|
|
170
|
+
return true;
|
|
171
|
+
if (DEVNET_EQUIVALENTS.has(canonA) && DEVNET_EQUIVALENTS.has(canonB))
|
|
172
|
+
return true;
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* @name getNetworkGenesisHash
|
|
177
|
+
* @description Get the genesis-hash form of a network identifier.
|
|
178
|
+
* Returns the genesis-hash variant for known networks, or the input as-is.
|
|
179
|
+
*
|
|
180
|
+
* Useful for agents that require the genesis-hash form (Kamiyo, Helius x402).
|
|
181
|
+
*
|
|
182
|
+
* @param networkId - Any network identifier.
|
|
183
|
+
* @returns The genesis-hash form, or the input if unknown.
|
|
184
|
+
*
|
|
185
|
+
* @category Utils
|
|
186
|
+
* @since v0.6.0
|
|
187
|
+
*/
|
|
188
|
+
function getNetworkGenesisHash(networkId) {
|
|
189
|
+
const norm = normalizeNetworkId(networkId);
|
|
190
|
+
if (MAINNET_EQUIVALENTS.has(networkId) || MAINNET_EQUIVALENTS.has(norm)) {
|
|
191
|
+
return network_1.SapNetwork.SOLANA_MAINNET_GENESIS;
|
|
192
|
+
}
|
|
193
|
+
if (DEVNET_EQUIVALENTS.has(networkId) || DEVNET_EQUIVALENTS.has(norm)) {
|
|
194
|
+
return network_1.SapNetwork.SOLANA_DEVNET;
|
|
195
|
+
}
|
|
196
|
+
return norm;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* @name getNetworkClusterName
|
|
200
|
+
* @description Get the cluster-name form of a network identifier.
|
|
201
|
+
* Returns the human-readable cluster name for known networks.
|
|
202
|
+
*
|
|
203
|
+
* Useful for providers that accept cluster names (Coinbase, Phantom).
|
|
204
|
+
*
|
|
205
|
+
* @param networkId - Any network identifier.
|
|
206
|
+
* @returns The cluster-name form, or the input if unknown.
|
|
207
|
+
*
|
|
208
|
+
* @category Utils
|
|
209
|
+
* @since v0.6.0
|
|
210
|
+
*/
|
|
211
|
+
function getNetworkClusterName(networkId) {
|
|
212
|
+
const norm = normalizeNetworkId(networkId);
|
|
213
|
+
if (MAINNET_EQUIVALENTS.has(networkId) || MAINNET_EQUIVALENTS.has(norm)) {
|
|
214
|
+
return network_1.SapNetwork.SOLANA_MAINNET;
|
|
215
|
+
}
|
|
216
|
+
if (DEVNET_EQUIVALENTS.has(networkId) || DEVNET_EQUIVALENTS.has(norm)) {
|
|
217
|
+
return network_1.SapNetwork.SOLANA_DEVNET_NAMED;
|
|
218
|
+
}
|
|
219
|
+
return norm;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* @name isKnownNetwork
|
|
223
|
+
* @description Check if a network identifier is recognized by the SDK.
|
|
224
|
+
*
|
|
225
|
+
* @param networkId - The network identifier to check.
|
|
226
|
+
* @returns `true` if the identifier maps to a known Solana network.
|
|
227
|
+
*
|
|
228
|
+
* @category Utils
|
|
229
|
+
* @since v0.6.0
|
|
230
|
+
*/
|
|
231
|
+
function isKnownNetwork(networkId) {
|
|
232
|
+
const trimmed = networkId.trim();
|
|
233
|
+
return MAINNET_EQUIVALENTS.has(trimmed) || DEVNET_EQUIVALENTS.has(trimmed) ||
|
|
234
|
+
NETWORK_ALIASES.has(trimmed);
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=network-normalizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-normalizer.js","sourceRoot":"","sources":["../../../src/utils/network-normalizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module utils/rpc-strategy
|
|
4
|
+
* @description Dual-connection RPC strategy and idempotent ATA creation.
|
|
5
|
+
*
|
|
6
|
+
* Solves two interoperability problems:
|
|
7
|
+
*
|
|
8
|
+
* 1. **WebSocket 400 loop**: Some authenticated RPCs reject WebSocket
|
|
9
|
+
* connections for SPL token operations. This module exposes a
|
|
10
|
+
* dual-connection strategy: primary RPC for SAP program calls,
|
|
11
|
+
* fallback public RPC for token operations.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Idempotent ATA creation**: Wraps `getOrCreateAssociatedTokenAccount`
|
|
14
|
+
* with retries so "account already exists" doesn't surface as a
|
|
15
|
+
* hard error.
|
|
16
|
+
*
|
|
17
|
+
* @category Utils
|
|
18
|
+
* @since v0.6.0
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.getRpcUrl = getRpcUrl;
|
|
22
|
+
exports.getFallbackRpcUrl = getFallbackRpcUrl;
|
|
23
|
+
exports.createDualConnection = createDualConnection;
|
|
24
|
+
exports.findATA = findATA;
|
|
25
|
+
exports.classifyAnchorError = classifyAnchorError;
|
|
26
|
+
exports.extractAnchorErrorCode = extractAnchorErrorCode;
|
|
27
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
29
|
+
// Default Public RPCs
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
31
|
+
/** Well-known public fallback RPCs by cluster. */
|
|
32
|
+
const PUBLIC_RPCS = {
|
|
33
|
+
"mainnet-beta": "https://api.mainnet-beta.solana.com",
|
|
34
|
+
devnet: "https://api.devnet.solana.com",
|
|
35
|
+
localnet: "http://localhost:8899",
|
|
36
|
+
};
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
38
|
+
// Public API
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
40
|
+
/**
|
|
41
|
+
* @name getRpcUrl
|
|
42
|
+
* @description Get the primary RPC URL from environment or config.
|
|
43
|
+
*
|
|
44
|
+
* Resolution order:
|
|
45
|
+
* 1. Explicit `config.primaryUrl`
|
|
46
|
+
* 2. `SOLANA_RPC_URL` env var
|
|
47
|
+
* 3. Cluster-appropriate public RPC
|
|
48
|
+
*
|
|
49
|
+
* @param config - Optional RPC configuration.
|
|
50
|
+
* @param cluster - Cluster hint (defaults to "mainnet-beta").
|
|
51
|
+
* @returns The resolved primary RPC URL.
|
|
52
|
+
*
|
|
53
|
+
* @category Utils
|
|
54
|
+
* @since v0.6.0
|
|
55
|
+
*/
|
|
56
|
+
function getRpcUrl(config, cluster = "mainnet-beta") {
|
|
57
|
+
if (config?.primaryUrl)
|
|
58
|
+
return config.primaryUrl;
|
|
59
|
+
// Environment variable
|
|
60
|
+
const envUrl = typeof process !== "undefined" ? process.env?.SOLANA_RPC_URL : undefined;
|
|
61
|
+
if (envUrl)
|
|
62
|
+
return envUrl;
|
|
63
|
+
return PUBLIC_RPCS[cluster] ?? PUBLIC_RPCS["mainnet-beta"];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @name getFallbackRpcUrl
|
|
67
|
+
* @description Get the fallback RPC URL for SPL token operations.
|
|
68
|
+
*
|
|
69
|
+
* This avoids the WebSocket-400 loop when the primary RPC is
|
|
70
|
+
* an authenticated endpoint that rejects token-related WebSocket
|
|
71
|
+
* subscriptions.
|
|
72
|
+
*
|
|
73
|
+
* @param config - Optional RPC configuration.
|
|
74
|
+
* @param cluster - Cluster hint (defaults to "mainnet-beta").
|
|
75
|
+
* @returns The resolved fallback RPC URL.
|
|
76
|
+
*
|
|
77
|
+
* @category Utils
|
|
78
|
+
* @since v0.6.0
|
|
79
|
+
*/
|
|
80
|
+
function getFallbackRpcUrl(config, cluster = "mainnet-beta") {
|
|
81
|
+
if (config?.fallbackUrl)
|
|
82
|
+
return config.fallbackUrl;
|
|
83
|
+
// Env fallback
|
|
84
|
+
const envUrl = typeof process !== "undefined"
|
|
85
|
+
? process.env?.SOLANA_FALLBACK_RPC_URL
|
|
86
|
+
: undefined;
|
|
87
|
+
if (envUrl)
|
|
88
|
+
return envUrl;
|
|
89
|
+
return PUBLIC_RPCS[cluster] ?? PUBLIC_RPCS["mainnet-beta"];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @name createDualConnection
|
|
93
|
+
* @description Create a dual-connection pair: primary for SAP program calls,
|
|
94
|
+
* fallback for SPL token operations.
|
|
95
|
+
*
|
|
96
|
+
* @param config - RPC configuration.
|
|
97
|
+
* @param cluster - Cluster hint.
|
|
98
|
+
* @returns A {@link DualConnection} with both connections.
|
|
99
|
+
*
|
|
100
|
+
* @category Utils
|
|
101
|
+
* @since v0.6.0
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const { primary, fallback } = createDualConnection({
|
|
106
|
+
* primaryUrl: "https://my-rpc.example.com",
|
|
107
|
+
* }, "mainnet-beta");
|
|
108
|
+
*
|
|
109
|
+
* // Use primary for SAP calls
|
|
110
|
+
* const provider = new AnchorProvider(primary, wallet, {});
|
|
111
|
+
*
|
|
112
|
+
* // Use fallback for SPL token account creation
|
|
113
|
+
* const ata = await getOrCreateATA(fallback, mint, owner);
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
function createDualConnection(config, cluster = "mainnet-beta") {
|
|
117
|
+
const commitment = config.commitment ?? "confirmed";
|
|
118
|
+
const primaryUrl = getRpcUrl(config, cluster);
|
|
119
|
+
const fallbackUrl = getFallbackRpcUrl(config, cluster);
|
|
120
|
+
return {
|
|
121
|
+
primary: new web3_js_1.Connection(primaryUrl, { commitment }),
|
|
122
|
+
fallback: new web3_js_1.Connection(fallbackUrl, { commitment }),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* @name findATA
|
|
127
|
+
* @description Derive the Associated Token Account address.
|
|
128
|
+
* Uses the standard ATA PDA derivation without importing the full
|
|
129
|
+
* `@solana/spl-token` package.
|
|
130
|
+
*
|
|
131
|
+
* @param owner - The token account owner.
|
|
132
|
+
* @param mint - The token mint.
|
|
133
|
+
* @param programId - Token program ID (defaults to TOKEN_PROGRAM_ID).
|
|
134
|
+
* @returns The derived ATA public key.
|
|
135
|
+
*
|
|
136
|
+
* @category Utils
|
|
137
|
+
* @since v0.6.0
|
|
138
|
+
*/
|
|
139
|
+
function findATA(owner, mint, programId = new web3_js_1.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")) {
|
|
140
|
+
const ASSOCIATED_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
141
|
+
const [ata] = web3_js_1.PublicKey.findProgramAddressSync([owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
142
|
+
return ata;
|
|
143
|
+
}
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
145
|
+
// Error Classification
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
147
|
+
/**
|
|
148
|
+
* Anchor error code to friendly message mapping.
|
|
149
|
+
* Converts cryptic Anchor errors into actionable messages.
|
|
150
|
+
*/
|
|
151
|
+
const ANCHOR_ERROR_MESSAGES = {
|
|
152
|
+
6000: "Agent already registered for this wallet",
|
|
153
|
+
6001: "Agent not found — register first",
|
|
154
|
+
6002: "Name exceeds maximum length (64 bytes)",
|
|
155
|
+
6003: "Description exceeds maximum length (256 bytes)",
|
|
156
|
+
6004: "Too many capabilities (max 10)",
|
|
157
|
+
6005: "Too many pricing tiers (max 5)",
|
|
158
|
+
6006: "Too many protocols (max 5)",
|
|
159
|
+
6007: "Feedback score out of range (0-1000)",
|
|
160
|
+
6008: "Unauthorized — only the agent owner can perform this action",
|
|
161
|
+
6009: "Escrow expired",
|
|
162
|
+
6010: "Insufficient escrow balance",
|
|
163
|
+
6011: "Max calls exceeded",
|
|
164
|
+
6012: "Invalid settlement — calls must be > 0",
|
|
165
|
+
6013: "Escrow not empty — withdraw balance before closing",
|
|
166
|
+
6014: "Invalid token program — make sure you passed TOKEN_PROGRAM_ID as the 3rd remaining account",
|
|
167
|
+
6015: "Vault already initialized",
|
|
168
|
+
6016: "Session already exists",
|
|
169
|
+
6017: "Session closed — cannot write to closed session",
|
|
170
|
+
6018: "Data exceeds maximum write size (750 bytes)",
|
|
171
|
+
6019: "Ring buffer overflow — seal before writing more",
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* @name classifyAnchorError
|
|
175
|
+
* @description Convert an Anchor error code into a friendly, actionable message.
|
|
176
|
+
*
|
|
177
|
+
* @param errorCode - The numeric Anchor error code.
|
|
178
|
+
* @returns A human-readable error message, or a generic message for unknown codes.
|
|
179
|
+
*
|
|
180
|
+
* @category Utils
|
|
181
|
+
* @since v0.6.0
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* try {
|
|
186
|
+
* await client.escrow.create(...);
|
|
187
|
+
* } catch (err) {
|
|
188
|
+
* const code = extractAnchorErrorCode(err);
|
|
189
|
+
* if (code !== null) {
|
|
190
|
+
* console.error(classifyAnchorError(code));
|
|
191
|
+
* }
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
function classifyAnchorError(errorCode) {
|
|
196
|
+
return (ANCHOR_ERROR_MESSAGES[errorCode] ??
|
|
197
|
+
`Unknown SAP program error (code ${errorCode}). Check the IDL for details.`);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* @name extractAnchorErrorCode
|
|
201
|
+
* @description Attempt to extract an Anchor error code from an Error object.
|
|
202
|
+
*
|
|
203
|
+
* Anchor errors typically have the structure `{ code: number, msg: string }`.
|
|
204
|
+
* This function handles both the direct `error.code` pattern and the
|
|
205
|
+
* `error.error.errorCode.number` nested pattern.
|
|
206
|
+
*
|
|
207
|
+
* @param err - The caught error object.
|
|
208
|
+
* @returns The numeric error code, or `null` if not an Anchor error.
|
|
209
|
+
*
|
|
210
|
+
* @category Utils
|
|
211
|
+
* @since v0.6.0
|
|
212
|
+
*/
|
|
213
|
+
function extractAnchorErrorCode(err) {
|
|
214
|
+
if (err == null || typeof err !== "object")
|
|
215
|
+
return null;
|
|
216
|
+
// Direct code property
|
|
217
|
+
const direct = err.code;
|
|
218
|
+
if (typeof direct === "number")
|
|
219
|
+
return direct;
|
|
220
|
+
// Nested Anchor format: error.error.errorCode.number
|
|
221
|
+
const nested = err.error;
|
|
222
|
+
if (nested && typeof nested === "object") {
|
|
223
|
+
const errorCode = nested.errorCode;
|
|
224
|
+
if (errorCode && typeof errorCode === "object") {
|
|
225
|
+
const num = errorCode.number;
|
|
226
|
+
if (typeof num === "number")
|
|
227
|
+
return num;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// AnchorError pattern: { logs: [...], error: { errorCode: { number } } }
|
|
231
|
+
const msg = err.message;
|
|
232
|
+
if (typeof msg === "string") {
|
|
233
|
+
const match = msg.match(/custom program error:\s*0x([0-9a-fA-F]+)/);
|
|
234
|
+
if (match?.[1])
|
|
235
|
+
return parseInt(match[1], 16);
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=rpc-strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-strategy.js","sourceRoot":"","sources":["../../../src/utils/rpc-strategy.ts"],"names":[],"mappings":"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"}
|