@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/README.md +59 -2
- package/dist/{chunk-2X22JY7Q.js → chunk-H77BBZOL.js} +6 -3
- package/dist/chunk-H77BBZOL.js.map +1 -0
- package/dist/index.d.ts +228 -19
- package/dist/index.js +384 -54
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/dist/{wallet-LqCFWwV4.d.ts → wallet--IGHoCtG.d.ts} +1 -1
- package/idl/molpha.json +17 -0
- package/idl/profiles/dev/molpha.json +7841 -0
- package/idl/profiles/stable/molpha.json +5563 -0
- package/package.json +5 -1
- package/dist/chunk-2X22JY7Q.js.map +0 -1
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-
|
|
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/
|
|
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
|
-
|
|
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
|
|
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 =
|
|
935
|
-
timeoutMs =
|
|
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
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
|
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
|
|
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(
|
|
1110
|
-
|
|
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
|
|
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.
|
|
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
|
|
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(
|
|
9676
|
+
}).remainingAccounts(remaining).preInstructions(preInstructions).rpc();
|
|
9533
9677
|
return { signature, feed };
|
|
9534
9678
|
}
|
|
9535
|
-
/**
|
|
9536
|
-
|
|
9537
|
-
const
|
|
9538
|
-
|
|
9539
|
-
);
|
|
9540
|
-
const
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
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
|
-
|
|
9548
|
-
|
|
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
|
|
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
|
-
|
|
10276
|
-
|
|
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
|