@molpha/sdk 0.2.0-dev-20261005112114 → 0.2.0-dev-20261006113121

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/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { toPublicKey, addressBytes, toSolanaAddress, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ADDRESS, SYSTEM_PROGRAM_ADDRESS, setComputeUnitLimit, addressFromBytes, findProgramAddressSync } from './chunk-2X22JY7Q.js';
1
+ import { toPublicKey, addressBytes, toSolanaAddress, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ADDRESS, SYSTEM_PROGRAM_ADDRESS, setComputeUnitLimit, setComputeUnitPrice, addressFromBytes, findProgramAddressSync } from './chunk-H77BBZOL.js';
2
2
  import { getAddressEncoder, address, getProgramDerivedAddress } from '@solana/kit';
3
3
  import { keccak_256 } from '@noble/hashes/sha3.js';
4
4
  import { utf8ToBytes, hexToBytes as hexToBytes$1, concatBytes as concatBytes$1, bytesToHex as bytesToHex$1, randomBytes } from '@noble/hashes/utils.js';
5
5
  import { secp256k1 } from '@noble/curves/secp256k1.js';
6
6
  import { gcm } from '@noble/ciphers/aes.js';
7
- import { AnchorProvider, Program } from '@anchor-lang/core';
7
+ import { AnchorProvider, Program, web3 } from '@anchor-lang/core';
8
8
  import BN from 'bn.js';
9
9
  import { ed25519 } from '@noble/curves/ed25519.js';
10
10
 
@@ -201,8 +201,13 @@ function deriveSourceIdString(apiConfig) {
201
201
  return bytesToHex(deriveSourceId(apiConfig));
202
202
  }
203
203
 
204
- // src/core/constants.ts
204
+ // src/deployment.generated.ts
205
+ var MOLPHA_SDK_PROFILE = "dev";
206
+ var DEFAULT_GATEWAY_ENDPOINT = "https://dev-gateway.molpha.io/";
205
207
  var MOLPHA_PROGRAM_ID = "chivcFQgxzwkpLvW41PV431HQ4dYpaW3povQH3AdpQt";
208
+ var MOLPHA_VERIFIER_ADDRESS = "0xE1fd792b7E54e0C8F0Cd1c8055E446ff36d233eB";
209
+ var MOLPHA_VERIFIER_STARKNET_SEPOLIA = "0x0378df4dbecf8f0c7daa801282932f7011c7a5e5773bab9eaf68f5fa5e7530ef";
210
+ var MOLPHA_DEV_VERIFIERS_PROVISIONAL = true;
206
211
 
207
212
  // src/core/int256.ts
208
213
  var INT256_MAX = (1n << 255n) - 1n;
@@ -775,7 +780,16 @@ function parseGatewayInfo(data) {
775
780
  if (programId !== void 0 && typeof programId !== "string") {
776
781
  throw new Error("GET /v1/info programId must be a base58 string");
777
782
  }
778
- return programId === void 0 ? { gatewayAuthority } : { gatewayAuthority, programId };
783
+ const info = { gatewayAuthority };
784
+ if (programId !== void 0) info.programId = programId;
785
+ const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
786
+ const tickMs = positive(record.tickMs);
787
+ if (tickMs !== void 0) info.tickMs = tickMs;
788
+ const roundTimeoutSeconds = positive(record.roundTimeoutSeconds);
789
+ if (roundTimeoutSeconds !== void 0) info.roundTimeoutSeconds = roundTimeoutSeconds;
790
+ const maxInflightRounds = positive(record.maxInflightRounds);
791
+ if (maxInflightRounds !== void 0) info.maxInflightRounds = maxInflightRounds;
792
+ return info;
779
793
  }
780
794
 
781
795
  // src/gateway/index.ts
@@ -786,7 +800,37 @@ function msUntilNextTick(nowMs, tickMs) {
786
800
  return tickMs - nowMs % tickMs + 1;
787
801
  }
788
802
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
789
- var DEFAULT_GATEWAY_ENDPOINT = "https://dev-gateway.molpha.io/";
803
+ var BACKOFF_BASE_MS = 250;
804
+ var BACKOFF_MAX_MS = 5e3;
805
+ var DEFAULT_ROUND_TIMEOUT_MS = 35e3;
806
+ var DEFAULT_MAX_RETRIES = 6;
807
+ function retryDelayMs(cause, failures, nowMs, tickMs, random = Math.random) {
808
+ const nextTick = msUntilNextTick(nowMs, tickMs);
809
+ switch (cause.kind) {
810
+ case "conflict":
811
+ return nextTick + random() * (tickMs / 2);
812
+ case "busy":
813
+ return Math.max(nextTick, cause.retryAfterMs ?? tickMs) + random() * tickMs;
814
+ case "error": {
815
+ const backoff = Math.min(BACKOFF_BASE_MS * 2 ** Math.max(failures - 1, 0), BACKOFF_MAX_MS);
816
+ return Math.max(nextTick, backoff / 2 + random() * (backoff / 2));
817
+ }
818
+ }
819
+ }
820
+ function mergeCause(a, b) {
821
+ if (a.kind === "busy" || b.kind === "busy") {
822
+ const waits = [a, b].flatMap((c) => c.kind === "busy" && c.retryAfterMs !== void 0 ? [c.retryAfterMs] : []);
823
+ return waits.length ? { kind: "busy", retryAfterMs: Math.max(...waits) } : { kind: "busy" };
824
+ }
825
+ if (a.kind === "conflict" || b.kind === "conflict") return { kind: "conflict" };
826
+ return { kind: "error" };
827
+ }
828
+ function retryAfterMs(res) {
829
+ const raw = res.headers.get("retry-after");
830
+ if (raw === null) return void 0;
831
+ const seconds = Number(raw);
832
+ return Number.isFinite(seconds) && seconds >= 0 ? Math.min(seconds, 60) * 1e3 : void 0;
833
+ }
790
834
  var GatewayError = class extends Error {
791
835
  constructor(message, status) {
792
836
  super(message);
@@ -808,6 +852,9 @@ var MolphaGateway = class {
808
852
  programIdBytes;
809
853
  /** Gateway PDA per endpoint URL; resolved once per client lifetime. */
810
854
  gatewayPdas = /* @__PURE__ */ new Map();
855
+ /** Advertised timing per endpoint URL, read lazily on the first retry. */
856
+ advertisedInfo = /* @__PURE__ */ new Map();
857
+ random;
811
858
  constructor(endpoints, getRegistrySelectionConfig = async () => {
812
859
  throw new Error(
813
860
  "MolphaGateway requires getRegistrySelectionConfig to request signed data \u2014 pass the current on-chain registry version, redundancy buffer and node count (e.g. () => solana.getRegistrySelectionConfig())"
@@ -828,6 +875,11 @@ var MolphaGateway = class {
828
875
  this.allowUnverifiedNodeKeysForPrivateApi = options.allowUnverifiedNodeKeysForPrivateApi ?? false;
829
876
  this.programId = options.programId ?? MOLPHA_PROGRAM_ID;
830
877
  this.programIdBytes = addressToBytes(this.programId);
878
+ this.random = options.random ?? Math.random;
879
+ }
880
+ /** The configured gateway base URLs, in failover order. */
881
+ endpointUrls() {
882
+ return this.endpoints.map((e) => e.url);
831
883
  }
832
884
  /** Tries endpoints in order; returns the first node list it can fetch. */
833
885
  async getNodes() {
@@ -931,8 +983,8 @@ var MolphaGateway = class {
931
983
  encrypt,
932
984
  sourcePayment,
933
985
  maxAge = 60,
934
- maxRetries = 15,
935
- timeoutMs = 5e3
986
+ maxRetries = DEFAULT_MAX_RETRIES,
987
+ timeoutMs = DEFAULT_ROUND_TIMEOUT_MS
936
988
  } = opts;
937
989
  const signaturesRequired = assertSignaturesRequired(opts.signaturesRequired);
938
990
  const verifyNodeKeys = opts.verifyNodeKeys ?? this.verifyNodeKeys;
@@ -954,17 +1006,19 @@ var MolphaGateway = class {
954
1006
  }
955
1007
  const consumerAuthority = opts.consumerAuthority ?? this.defaultConsumerAuthority ?? subscriptionOwner;
956
1008
  const authSigner = signer ?? this.defaultSigner;
957
- const round = await this.resolveContext(opts.context, encrypt !== void 0);
958
- const { registryVersion, redundancyBuffer } = round;
959
- if (round.nodeCount !== void 0 && round.nodes !== void 0 && round.nodes.length !== round.nodeCount) {
960
- throw new Error(
961
- `Gateway node list has ${round.nodes.length} nodes but registry ${registryVersion} has node_count ${round.nodeCount} \u2014 refresh the round context`
962
- );
963
- }
964
- const nodeCount = round.nodeCount ?? round.nodes?.length;
965
- if (nodeCount === void 0) {
966
- throw new Error("Round context resolved neither nodeCount nor nodes");
967
- }
1009
+ let round = await this.resolveContext(opts.context, encrypt !== void 0);
1010
+ let { registryVersion, redundancyBuffer } = round;
1011
+ const nodeCountOf = (r) => {
1012
+ if (r.nodeCount !== void 0 && r.nodes !== void 0 && r.nodes.length !== r.nodeCount) {
1013
+ throw new Error(
1014
+ `Gateway node list has ${r.nodes.length} nodes but registry ${r.registryVersion} has node_count ${r.nodeCount} \u2014 refresh the round context`
1015
+ );
1016
+ }
1017
+ const n = r.nodeCount ?? r.nodes?.length;
1018
+ if (n === void 0) throw new Error("Round context resolved neither nodeCount nor nodes");
1019
+ return n;
1020
+ };
1021
+ let nodeCount = nodeCountOf(round);
968
1022
  let terms = null;
969
1023
  let authorizations = 0;
970
1024
  let requoted = false;
@@ -987,11 +1041,19 @@ var MolphaGateway = class {
987
1041
  }
988
1042
  }
989
1043
  let lastError;
990
- const tickMs = opts.tickMs ?? DEFAULT_TICK_MS;
991
- const groupSize = effectiveSelectionSize(signaturesRequired, redundancyBuffer, nodeCount);
992
- const registryIndexes = Array.from({ length: nodeCount }, (_, index) => index);
1044
+ let tickMs = opts.tickMs;
1045
+ let groupSize = effectiveSelectionSize(signaturesRequired, redundancyBuffer, nodeCount);
1046
+ let registryIndexes = Array.from({ length: nodeCount }, (_, index) => index);
1047
+ let cause = { kind: "error" };
1048
+ let refreshedOnce = false;
1049
+ let retryNow = false;
993
1050
  for (let attempt = 0; attempt < maxRetries; attempt++) {
994
- if (attempt > 0) await sleep(msUntilNextTick(Date.now(), tickMs));
1051
+ if (attempt > 0 && !retryNow) {
1052
+ tickMs ??= await this.advertisedTickMs(timeoutMs) ?? DEFAULT_TICK_MS;
1053
+ await sleep(retryDelayMs(cause, attempt, Date.now(), tickMs, this.random));
1054
+ }
1055
+ retryNow = false;
1056
+ cause = { kind: "error" };
995
1057
  let encKeyBundle;
996
1058
  if (encrypt) {
997
1059
  const recipients = selectedNodesForPrivateApiEncryption(round.nodes ?? [], registryIndexes);
@@ -1025,6 +1087,7 @@ var MolphaGateway = class {
1025
1087
  if (encKeyBundle) baseBody.encKeyBundle = encKeyBundle;
1026
1088
  if (sourcePayments) baseBody.sourcePayments = sourcePayments;
1027
1089
  let requote = false;
1090
+ let restartForRefresh = false;
1028
1091
  for (const endpoint of this.endpoints) {
1029
1092
  try {
1030
1093
  let authSig = ZERO_AUTH_SIG;
@@ -1098,17 +1161,27 @@ var MolphaGateway = class {
1098
1161
  break;
1099
1162
  }
1100
1163
  const errorDetail = !res.ok ? await parseGatewayErrorDetail(res) : void 0;
1101
- if (res.status === 400 || res.status === 401) {
1164
+ if (res.status === 400 && !refreshedOnce && !restartForRefresh && /registryVersion.*current version/i.test(errorDetail ?? "")) {
1165
+ restartForRefresh = true;
1166
+ break;
1167
+ }
1168
+ if (res.status === 400 || res.status === 401 || res.status === 403) {
1102
1169
  throw new GatewayError(
1103
1170
  formatGatewayErrorMessage("Gateway rejected request", res.status, errorDetail),
1104
1171
  res.status
1105
1172
  );
1106
1173
  }
1107
- if (res.status === 503) {
1174
+ if (res.status === 503 || res.status === 429) {
1108
1175
  lastError = new GatewayError(
1109
- formatGatewayErrorMessage("Gateway unavailable", 503, errorDetail),
1110
- 503
1176
+ formatGatewayErrorMessage(
1177
+ res.status === 429 ? "Gateway rate limited" : "Gateway unavailable",
1178
+ res.status,
1179
+ errorDetail
1180
+ ),
1181
+ res.status
1111
1182
  );
1183
+ const wait = retryAfterMs(res);
1184
+ cause = mergeCause(cause, wait === void 0 ? { kind: "busy" } : { kind: "busy", retryAfterMs: wait });
1112
1185
  continue;
1113
1186
  }
1114
1187
  if (!res.ok) {
@@ -1116,6 +1189,7 @@ var MolphaGateway = class {
1116
1189
  formatGatewayErrorMessage("Gateway error", res.status, errorDetail),
1117
1190
  res.status
1118
1191
  );
1192
+ if (res.status === 409) cause = mergeCause(cause, { kind: "conflict" });
1119
1193
  continue;
1120
1194
  }
1121
1195
  const json = await res.json();
@@ -1129,12 +1203,23 @@ var MolphaGateway = class {
1129
1203
  lastError = new Error(`Gateway returned status: ${json.status}`);
1130
1204
  } catch (err) {
1131
1205
  if (err instanceof UpstreamPaymentRequiredError) throw err;
1132
- if (err instanceof GatewayError && (err.status === 400 || err.status === 401 || err.status === 402)) {
1206
+ if (err instanceof GatewayError && (err.status === 400 || err.status === 401 || err.status === 402 || err.status === 403)) {
1133
1207
  throw err;
1134
1208
  }
1135
1209
  lastError = err;
1136
1210
  }
1137
1211
  }
1212
+ if (restartForRefresh) {
1213
+ round = await this.resolveContext(void 0, encrypt !== void 0);
1214
+ ({ registryVersion, redundancyBuffer } = round);
1215
+ nodeCount = nodeCountOf(round);
1216
+ groupSize = effectiveSelectionSize(signaturesRequired, redundancyBuffer, nodeCount);
1217
+ registryIndexes = Array.from({ length: nodeCount }, (_, index) => index);
1218
+ refreshedOnce = true;
1219
+ retryNow = true;
1220
+ attempt--;
1221
+ continue;
1222
+ }
1138
1223
  if (requote) continue;
1139
1224
  }
1140
1225
  throw lastError instanceof Error ? lastError : new Error("Gateway round failed after retries");
@@ -1163,6 +1248,27 @@ var MolphaGateway = class {
1163
1248
  if (registry.nodeCount !== void 0) return registry;
1164
1249
  return { ...registry, nodes: await this.getNodes() };
1165
1250
  }
1251
+ /**
1252
+ * The gateway's advertised tick grid, read once per endpoint and only when a retry needs it. Any
1253
+ * failure (an old gateway without `/v1/info`, a timeout) means "not advertised", so the caller
1254
+ * falls back to the default; a failure is not cached.
1255
+ */
1256
+ async advertisedTickMs(timeoutMs) {
1257
+ for (const endpoint of this.endpoints) {
1258
+ let pending = this.advertisedInfo.get(endpoint.url);
1259
+ if (!pending) {
1260
+ pending = this.fetchGatewayInfo(endpoint, Math.min(timeoutMs, 3e3)).catch(() => void 0);
1261
+ this.advertisedInfo.set(endpoint.url, pending);
1262
+ }
1263
+ const info = await pending;
1264
+ if (info === void 0) {
1265
+ this.advertisedInfo.delete(endpoint.url);
1266
+ continue;
1267
+ }
1268
+ if (info.tickMs !== void 0) return info.tickMs;
1269
+ }
1270
+ return void 0;
1271
+ }
1166
1272
  /** Gateway PDA bytes for an endpoint, cached per URL. A failed lookup is not cached. */
1167
1273
  resolveGatewayPda(endpoint, timeoutMs) {
1168
1274
  let pending = this.gatewayPdas.get(endpoint.url);
@@ -6475,6 +6581,11 @@ var molpha_default = {
6475
6581
  code: 6100,
6476
6582
  name: "InvalidRentReceiver",
6477
6583
  msg: "rent receiver must be the account that paid for the closed account"
6584
+ },
6585
+ {
6586
+ code: 6101,
6587
+ name: "InvalidInitialTau",
6588
+ msg: "initial_tau is below the minimum ticket threshold"
6478
6589
  }
6479
6590
  ],
6480
6591
  types: [
@@ -7514,6 +7625,18 @@ var molpha_default = {
7514
7625
  {
7515
7626
  name: "protocol_fee_bps",
7516
7627
  type: "u16"
7628
+ },
7629
+ {
7630
+ name: "initial_tau",
7631
+ docs: [
7632
+ "Ticket threshold of the two genesis epochs: a round is a ticket when the 64-bit prefix of its",
7633
+ "ticket hash is below it, so the sampling probability is `initial_tau / 2^64`. Size it as",
7634
+ "`2^64 * target_tickets_per_epoch / expected_rounds_per_epoch`. `u64::MAX` samples every round",
7635
+ "(every round then costs a ticket transaction until the controller, which moves the",
7636
+ "threshold at most 8x per epoch with a two-epoch lag, brings it down). Must be at least",
7637
+ "`TAU_MIN`."
7638
+ ],
7639
+ type: "u64"
7517
7640
  }
7518
7641
  ]
7519
7642
  }
@@ -9347,7 +9470,17 @@ function planIdFromVariant(variant) {
9347
9470
  }
9348
9471
 
9349
9472
  // src/solana/client.ts
9350
- var DEFAULT_COMPUTE_UNIT_LIMIT = 14e5;
9473
+ var MAX_COMPUTE_UNIT_LIMIT = 14e5;
9474
+ function estimateSubmitComputeUnits(signerCount) {
9475
+ if (!Number.isInteger(signerCount) || signerCount < 1) return MAX_COMPUTE_UNIT_LIMIT;
9476
+ const measured = 43e3 + 9200 * signerCount;
9477
+ return Math.min(Math.round(measured * 1.15) + 1e4, MAX_COMPUTE_UNIT_LIMIT);
9478
+ }
9479
+ var REGISTRY_CACHE_MS = 3e4;
9480
+ var KEY_CACHE_LIMIT = 8192;
9481
+ var PRIORITY_FEE_CACHE_MS = 5e3;
9482
+ var AUTO_FEE_PERCENTILE = 0.75;
9483
+ var AUTO_FEE_CAP_MICRO_LAMPORTS = 1e6;
9351
9484
  var MolphaSolanaClient = class _MolphaSolanaClient {
9352
9485
  constructor(program, provider, programId) {
9353
9486
  this.program = program;
@@ -9357,6 +9490,13 @@ var MolphaSolanaClient = class _MolphaSolanaClient {
9357
9490
  program;
9358
9491
  provider;
9359
9492
  programId;
9493
+ // Registry reads and signer keys are shared by every submit that names the same registry
9494
+ // version, so a fleet of feeds reads them once. Promises are cached, so concurrent submits
9495
+ // coalesce; a failed read is dropped and retried by the next call.
9496
+ registryCache = /* @__PURE__ */ new Map();
9497
+ nodeKeyCache = /* @__PURE__ */ new Map();
9498
+ coalitionCache = /* @__PURE__ */ new Map();
9499
+ priorityFeeCache;
9360
9500
  static create(opts) {
9361
9501
  const programId = toSolanaAddress(opts.programId ?? MOLPHA_PROGRAM_ADDRESS);
9362
9502
  const provider = new AnchorProvider(opts.connection, opts.wallet, {
@@ -9515,12 +9655,16 @@ var MolphaSolanaClient = class _MolphaSolanaClient {
9515
9655
  const { payload, signature: schnorr } = attestation;
9516
9656
  const sourceId = toFixedBytes(payload.sourceId, 32, "sourceId");
9517
9657
  const submitter = this.wallet;
9518
- const registry = await this.fetchRegistry(payload.registryVersion);
9658
+ const registry = await this.fetchRegistryCached(payload.registryVersion);
9519
9659
  const remaining = resolveRemainingAccounts(schnorr.signersBitmap, registry);
9520
9660
  assertSignerCount(remaining.length, payload.signaturesRequired, registry);
9521
- const coalitionKey = opts?.coalitionKey ?? await this.computeSignerCoalitionKey(remaining, registry);
9661
+ const coalitionKey = opts?.coalitionKey ?? await this.computeSignerCoalitionKey(remaining, registry, schnorr.signersBitmap);
9522
9662
  const feed = feedPda(sourceId, payload.signaturesRequired, submitter, this.programId);
9523
- const cuIx = setComputeUnitLimit(opts?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT);
9663
+ const preInstructions = [
9664
+ setComputeUnitLimit(opts?.computeUnitLimit ?? estimateSubmitComputeUnits(remaining.length))
9665
+ ];
9666
+ const price = await this.resolvePriorityFee(opts?.priorityFeeMicroLamports, feed);
9667
+ if (price > 0) preInstructions.push(setComputeUnitPrice(price));
9524
9668
  const signature = await this.methods.submitAttestation(
9525
9669
  buildSubmitAttestationArgs(attestation, coalitionKey, opts?.rawValue)
9526
9670
  ).accountsPartial({
@@ -9529,26 +9673,80 @@ var MolphaSolanaClient = class _MolphaSolanaClient {
9529
9673
  feed,
9530
9674
  protocolConfig: protocolConfigPda(this.programId),
9531
9675
  systemProgram: SYSTEM_PROGRAM_ADDRESS
9532
- }).remainingAccounts(remaining).preInstructions([cuIx]).rpc();
9676
+ }).remainingAccounts(remaining).preInstructions(preInstructions).rpc();
9533
9677
  return { signature, feed };
9534
9678
  }
9535
- /** Sum of the signers' keys, read from their on-chain `Node` accounts (one batched fetch). */
9536
- async computeSignerCoalitionKey(remaining, registry) {
9537
- const accounts = await this.accounts.node.fetchMultiple(
9538
- remaining.map((meta) => meta.pubkey)
9539
- );
9540
- const keys = accounts.map((account, i) => {
9541
- if (!account) {
9542
- throw new Error(
9543
- `Node account ${remaining[i].pubkey.toBase58()} (registry ${registry.version}) does not exist`
9544
- );
9679
+ /** The registry for `version`, reused for {@link REGISTRY_CACHE_MS}. */
9680
+ fetchRegistryCached(version) {
9681
+ const now = Date.now();
9682
+ const hit = this.registryCache.get(version);
9683
+ if (hit && now - hit.at < REGISTRY_CACHE_MS) return hit.value;
9684
+ const value = this.fetchRegistry(version);
9685
+ this.registryCache.set(version, { at: now, value });
9686
+ value.catch(() => {
9687
+ if (this.registryCache.get(version)?.value === value) this.registryCache.delete(version);
9688
+ });
9689
+ return value;
9690
+ }
9691
+ async resolvePriorityFee(option, feed) {
9692
+ if (option === void 0) return 0;
9693
+ if (option !== "auto") {
9694
+ if (!Number.isFinite(option) || option < 0) {
9695
+ throw new Error('priorityFeeMicroLamports must be a non-negative number or "auto"');
9545
9696
  }
9546
- return {
9547
- x: nodeCoordinate(account, "secp256k1PubkeyX", "secp256k1_pubkey_x"),
9548
- y: nodeCoordinate(account, "secp256k1PubkeyY", "secp256k1_pubkey_y")
9549
- };
9697
+ return Math.floor(option);
9698
+ }
9699
+ const now = Date.now();
9700
+ if (!this.priorityFeeCache || now - this.priorityFeeCache.at >= PRIORITY_FEE_CACHE_MS) {
9701
+ const value = (async () => {
9702
+ try {
9703
+ const recent = await this.provider.connection.getRecentPrioritizationFees({
9704
+ lockedWritableAccounts: [new web3.PublicKey(feed)]
9705
+ });
9706
+ const fees = recent.map((r) => r.prioritizationFee).sort((a, b) => a - b);
9707
+ if (fees.length === 0) return 0;
9708
+ const fee = fees[Math.min(fees.length - 1, Math.floor(fees.length * AUTO_FEE_PERCENTILE))];
9709
+ return Math.min(fee, AUTO_FEE_CAP_MICRO_LAMPORTS);
9710
+ } catch {
9711
+ return 0;
9712
+ }
9713
+ })();
9714
+ this.priorityFeeCache = { at: now, value };
9715
+ }
9716
+ return this.priorityFeeCache.value;
9717
+ }
9718
+ /** Sum of the signers' keys, read from their on-chain `Node` accounts (one batched fetch). */
9719
+ computeSignerCoalitionKey(remaining, registry, signersBitmap) {
9720
+ const setKey = `${registry.version}:${signersBitmap}`;
9721
+ const known = this.coalitionCache.get(setKey);
9722
+ if (known) return known;
9723
+ if (this.coalitionCache.size >= KEY_CACHE_LIMIT) this.coalitionCache.clear();
9724
+ const pending = this.sumSignerKeys(remaining, registry);
9725
+ this.coalitionCache.set(setKey, pending);
9726
+ pending.catch(() => {
9727
+ if (this.coalitionCache.get(setKey) === pending) this.coalitionCache.delete(setKey);
9550
9728
  });
9551
- return computeCoalitionKey(keys);
9729
+ return pending;
9730
+ }
9731
+ async sumSignerKeys(remaining, registry) {
9732
+ const addresses = remaining.map((meta) => meta.pubkey);
9733
+ const missing = addresses.filter((a) => !this.nodeKeyCache.has(a.toBase58()));
9734
+ if (missing.length > 0) {
9735
+ const accounts = await this.accounts.node.fetchMultiple(missing);
9736
+ if (this.nodeKeyCache.size + missing.length > KEY_CACHE_LIMIT) this.nodeKeyCache.clear();
9737
+ accounts.forEach((account, i) => {
9738
+ if (!account) {
9739
+ throw new Error(
9740
+ `Node account ${missing[i].toBase58()} (registry ${registry.version}) does not exist`
9741
+ );
9742
+ }
9743
+ this.nodeKeyCache.set(missing[i].toBase58(), {
9744
+ x: nodeCoordinate(account, "secp256k1PubkeyX", "secp256k1_pubkey_x"),
9745
+ y: nodeCoordinate(account, "secp256k1PubkeyY", "secp256k1_pubkey_y")
9746
+ });
9747
+ });
9748
+ }
9749
+ return computeCoalitionKey(addresses.map((a) => this.nodeKeyCache.get(a.toBase58())));
9552
9750
  }
9553
9751
  /**
9554
9752
  * Read the feed written by `submitter` (default: this wallet) for
@@ -9976,9 +10174,6 @@ var MOLPHA_VERIFIER_ABI = [
9976
10174
  { type: "error", name: "InvalidRegistryVersion", inputs: [] }
9977
10175
  ];
9978
10176
 
9979
- // src/evm/constants.ts
9980
- var MOLPHA_VERIFIER_ADDRESS = "0xE1fd792b7E54e0C8F0Cd1c8055E446ff36d233eB";
9981
-
9982
10177
  // src/evm/helpers.ts
9983
10178
  var VERIFY_SELECTOR = hexToBytes("67e2907b");
9984
10179
  var U8_MAX = 0xffn;
@@ -10107,7 +10302,6 @@ function parseEvmVerifyResult(result) {
10107
10302
  }
10108
10303
 
10109
10304
  // src/starknet/constants.ts
10110
- var MOLPHA_VERIFIER_STARKNET_SEPOLIA = "0x0378df4dbecf8f0c7daa801282932f7011c7a5e5773bab9eaf68f5fa5e7530ef";
10111
10305
  var MOLPHA_VERIFIER_STARKNET_ADDRESSES = {
10112
10306
  "starknet-sepolia": MOLPHA_VERIFIER_STARKNET_SEPOLIA
10113
10307
  };
@@ -10242,7 +10436,139 @@ function parseStarknetVerifyResult(result) {
10242
10436
  return { success, code, reason: verifyCodeName(code) ?? "UNKNOWN" };
10243
10437
  }
10244
10438
 
10439
+ // src/bulk.ts
10440
+ var DEFAULT_CONCURRENCY = 32;
10441
+ var MAX_DEFAULT_CONCURRENCY = 64;
10442
+ var DEFAULT_SUBMIT_CONCURRENCY = 8;
10443
+ var BACKOFF_COOLDOWN_MS = 1e3;
10444
+ var AdaptiveLimiter = class {
10445
+ constructor(initial, max, now = Date.now) {
10446
+ this.max = max;
10447
+ this.now = now;
10448
+ this.max = Math.max(1, max);
10449
+ this.limit_ = Math.min(Math.max(1, Math.floor(initial)), this.max);
10450
+ }
10451
+ max;
10452
+ now;
10453
+ limit_;
10454
+ inFlight = 0;
10455
+ successes = 0;
10456
+ lastCut = -Infinity;
10457
+ waiters = [];
10458
+ get limit() {
10459
+ return this.limit_;
10460
+ }
10461
+ async acquire() {
10462
+ if (this.inFlight < this.limit_) {
10463
+ this.inFlight++;
10464
+ return;
10465
+ }
10466
+ await new Promise((resolve) => this.waiters.push(resolve));
10467
+ }
10468
+ /** Release a slot. `busy` reports that the gateway answered at capacity. */
10469
+ release(outcome) {
10470
+ this.inFlight--;
10471
+ if (outcome === "busy") {
10472
+ this.successes = 0;
10473
+ const t = this.now();
10474
+ if (t - this.lastCut >= BACKOFF_COOLDOWN_MS) {
10475
+ this.limit_ = Math.max(1, Math.floor(this.limit_ / 2));
10476
+ this.lastCut = t;
10477
+ }
10478
+ } else if (outcome === "ok") {
10479
+ if (++this.successes >= this.limit_ && this.limit_ < this.max) {
10480
+ this.limit_++;
10481
+ this.successes = 0;
10482
+ }
10483
+ }
10484
+ while (this.waiters.length > 0 && this.inFlight < this.limit_) {
10485
+ this.inFlight++;
10486
+ this.waiters.shift()();
10487
+ }
10488
+ }
10489
+ };
10490
+ function spreadOffsetMs(sourceIdHex, spreadMs) {
10491
+ if (spreadMs <= 0) return 0;
10492
+ return Number.parseInt(sourceIdHex.slice(0, 8), 16) % spreadMs;
10493
+ }
10494
+ var isBusy = (err) => err instanceof GatewayError && (err.status === 503 || err.status === 429);
10495
+ async function requestMany(sdk, feeds, opts = {}) {
10496
+ const results = new Array(feeds.length);
10497
+ if (feeds.length === 0) return results;
10498
+ const advertised = await sdk.gateway.fetchGatewayInfo(sdkFirstEndpoint(sdk)).catch(() => void 0);
10499
+ const ceiling = opts.concurrency ?? (advertised?.maxInflightRounds ? Math.min(Math.max(1, Math.floor(advertised.maxInflightRounds / 2)), MAX_DEFAULT_CONCURRENCY) : DEFAULT_CONCURRENCY);
10500
+ const requests = new AdaptiveLimiter(ceiling, ceiling);
10501
+ const submits = new AdaptiveLimiter(
10502
+ opts.submitConcurrency ?? DEFAULT_SUBMIT_CONCURRENCY,
10503
+ opts.submitConcurrency ?? DEFAULT_SUBMIT_CONCURRENCY
10504
+ );
10505
+ const context = await sdk.gateway.prepareContext();
10506
+ const submit = opts.submit ?? true;
10507
+ const started = Date.now();
10508
+ const one = async (index) => {
10509
+ const feed = feeds[index];
10510
+ const result = { index, ok: false, ...feed.label !== void 0 ? { label: feed.label } : {} };
10511
+ results[index] = result;
10512
+ try {
10513
+ const wait = spreadOffsetMs(deriveSourceIdString(feed.apiConfig), opts.spreadMs ?? 0) - (Date.now() - started);
10514
+ if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
10515
+ if (opts.signal?.aborted) {
10516
+ result.error = new Error("aborted before the feed was started");
10517
+ return;
10518
+ }
10519
+ await requests.acquire();
10520
+ let outcome = "other";
10521
+ try {
10522
+ result.attestation = await sdk.gateway.requestSignedData({
10523
+ ...opts.request,
10524
+ apiConfig: feed.apiConfig,
10525
+ signaturesRequired: feed.signaturesRequired,
10526
+ context
10527
+ });
10528
+ result.ok = true;
10529
+ outcome = "ok";
10530
+ } catch (err) {
10531
+ result.error = err;
10532
+ outcome = isBusy(err) ? "busy" : "other";
10533
+ } finally {
10534
+ requests.release(outcome);
10535
+ }
10536
+ if (result.ok && submit && result.attestation) {
10537
+ await submits.acquire();
10538
+ try {
10539
+ const sent = await sdk.solana.submitAttestation(result.attestation);
10540
+ result.signature = sent.signature;
10541
+ result.feedAddress = sent.feed;
10542
+ } catch (err) {
10543
+ result.submitError = err;
10544
+ } finally {
10545
+ submits.release("other");
10546
+ }
10547
+ }
10548
+ } finally {
10549
+ opts.onResult?.(result);
10550
+ }
10551
+ };
10552
+ await Promise.all(feeds.map((_, index) => one(index)));
10553
+ return results;
10554
+ }
10555
+ function sdkFirstEndpoint(sdk) {
10556
+ return sdk.gateway.endpointUrls()[0];
10557
+ }
10558
+
10245
10559
  // src/index.ts
10560
+ var SubmitFailedError = class extends Error {
10561
+ constructor(result, cause) {
10562
+ super(
10563
+ `Gateway round succeeded but submitting the attestation failed: ${cause instanceof Error ? cause.message : String(cause)}`
10564
+ );
10565
+ this.result = result;
10566
+ this.cause = cause;
10567
+ this.name = "SubmitFailedError";
10568
+ }
10569
+ result;
10570
+ cause;
10571
+ };
10246
10572
  var MolphaSDK = class {
10247
10573
  gateway;
10248
10574
  solana;
@@ -10272,11 +10598,15 @@ var MolphaSDK = class {
10272
10598
  */
10273
10599
  async requestAndSubmit(opts) {
10274
10600
  const result = await this.gateway.requestSignedData(opts);
10275
- const { signature, feed } = await this.solana.submitAttestation(result);
10276
- return { result, signature, feed };
10601
+ try {
10602
+ const { signature, feed } = await this.solana.submitAttestation(result);
10603
+ return { result, signature, feed };
10604
+ } catch (cause) {
10605
+ throw new SubmitFailedError(result, cause);
10606
+ }
10277
10607
  }
10278
10608
  };
10279
10609
 
10280
- export { AggregationConfigError, DEFAULT_GATEWAY_ENDPOINT, EIP712_DOMAIN_TYPEHASH, GatewayError, INT256_MAX, INT256_MIN, MESSAGE_PREFIX, MIN_TOLERANCE_SIGNATURES, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_VERIFIER_ABI, MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_STARKNET_ADDRESSES, MOLPHA_VERIFIER_STARKNET_SEPOLIA, MolphaGateway, MolphaSDK, MolphaSolanaClient, PlanType, REQUEST_AUTH_DOMAIN, SELECTION_WINDOW_MS, TRANSFER_WITH_AUTHORIZATION_TYPEHASH, UpstreamPaymentRequiredError, VERIFY_CODES, addressToBytes, assertAggregationQuorum, attestationMessageHash, attestationMessageHashFromAttestation, base64ToBytes, bigIntFromBytesBe, bitmapBitSet, bitmapToIndices, buildEvmVerifierArgs, buildStarknetVerifierArgs, buildSubmitAttestationArgs, bytesToBase64, bytesToHex, bytesToHex0x, canonicalizeAPIConfig, canonicalizeAggregation, commitmentAddressToStarknetFelt, computeCoalitionKey, concatBytes, createEvmSignerFromPrivateKey, decodeInt256, deriveGatewayPda, deriveGatewayPdaAddress, deriveGroupBitmap, deriveSelectionBitmap, deriveSelectionSeed, deriveSourceId, deriveSourceIdString, domainSeparator, effectiveSelectionSize, eligibleSetSize, encodeEvmVerifyCalldata, encodeInt256, encodeInt256Decimal, encodeRequestAuth, encodeStarknetVerifyCalldata, ensureLength, evmAddressFromPrivateKey, feedPda, formatInt256Decimal, gatewayPda, gatewaySignerFromWallet, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, msUntilNextTick, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseEvmVerifyResult, parseGatewayInfo, parseStarknetVerifyResult, parseUpstreamQuote, planIdFromVariant, planPda, planVariant, probeSource, protocolConfigPda, registryPda, registryStatePda, resolveRemainingAccounts, secp256k1PublicKeyFromCoordinates, selectedIndices, signSourcePayments, signerFromKeypair, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, subscriptionPda, timestampAgeSeconds, timestampSeconds, toChecksumAddress, toFixedBytes, toFixedHex, transferWithAuthorizationDigest, transferWithAuthorizationHash, u256beFromBigInt, u32be, u32le, u64be, u64le, u8, utf8, validateSuppliedTerms, verifyCodeName };
10610
+ export { AdaptiveLimiter, AggregationConfigError, DEFAULT_GATEWAY_ENDPOINT, DEFAULT_MAX_RETRIES, DEFAULT_ROUND_TIMEOUT_MS, EIP712_DOMAIN_TYPEHASH, GatewayError, INT256_MAX, INT256_MIN, MESSAGE_PREFIX, MIN_TOLERANCE_SIGNATURES, MOLPHA_DEV_VERIFIERS_PROVISIONAL, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_SDK_PROFILE, MOLPHA_VERIFIER_ABI, MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_STARKNET_ADDRESSES, MOLPHA_VERIFIER_STARKNET_SEPOLIA, MolphaGateway, MolphaSDK, MolphaSolanaClient, PlanType, REQUEST_AUTH_DOMAIN, SELECTION_WINDOW_MS, SubmitFailedError, TRANSFER_WITH_AUTHORIZATION_TYPEHASH, UpstreamPaymentRequiredError, VERIFY_CODES, addressToBytes, assertAggregationQuorum, attestationMessageHash, attestationMessageHashFromAttestation, base64ToBytes, bigIntFromBytesBe, bitmapBitSet, bitmapToIndices, buildEvmVerifierArgs, buildStarknetVerifierArgs, buildSubmitAttestationArgs, bytesToBase64, bytesToHex, bytesToHex0x, canonicalizeAPIConfig, canonicalizeAggregation, commitmentAddressToStarknetFelt, computeCoalitionKey, concatBytes, createEvmSignerFromPrivateKey, decodeInt256, deriveGatewayPda, deriveGatewayPdaAddress, deriveGroupBitmap, deriveSelectionBitmap, deriveSelectionSeed, deriveSourceId, deriveSourceIdString, domainSeparator, effectiveSelectionSize, eligibleSetSize, encodeEvmVerifyCalldata, encodeInt256, encodeInt256Decimal, encodeRequestAuth, encodeStarknetVerifyCalldata, ensureLength, estimateSubmitComputeUnits, evmAddressFromPrivateKey, feedPda, formatInt256Decimal, gatewayPda, gatewaySignerFromWallet, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, msUntilNextTick, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseEvmVerifyResult, parseGatewayInfo, parseStarknetVerifyResult, parseUpstreamQuote, planIdFromVariant, planPda, planVariant, probeSource, protocolConfigPda, registryPda, registryStatePda, requestMany, resolveRemainingAccounts, retryDelayMs, secp256k1PublicKeyFromCoordinates, selectedIndices, signSourcePayments, signerFromKeypair, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, spreadOffsetMs, subscriptionPda, timestampAgeSeconds, timestampSeconds, toChecksumAddress, toFixedBytes, toFixedHex, transferWithAuthorizationDigest, transferWithAuthorizationHash, u256beFromBigInt, u32be, u32le, u64be, u64le, u8, utf8, validateSuppliedTerms, verifyCodeName };
10281
10611
  //# sourceMappingURL=index.js.map
10282
10612
  //# sourceMappingURL=index.js.map