@anywalletkit/extension-kit 0.3.6 → 0.5.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/README.md +28 -7
- package/dist/index.cjs +867 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +481 -17
- package/dist/index.d.ts +481 -17
- package/dist/index.js +868 -57
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -135,7 +135,7 @@ var Vault = class {
|
|
|
135
135
|
if (isEnvelopeV1(envelope)) {
|
|
136
136
|
if (envelope.kdf.iterations < VAULT_MIN_ITERATIONS) throw new VaultError(`The vault's KDF (${envelope.kdf.iterations} iterations) is below the floor this build accepts.`, "weak-kdf");
|
|
137
137
|
const kek2 = await this._derivePbkdf2(password, fromBase64(envelope.kdf.salt), envelope.kdf.iterations);
|
|
138
|
-
const dek = await this._open(kek2, envelope.dek, aadDekV1(envelope.kdf), "wrong-password");
|
|
138
|
+
const dek = await this._proveOwn(await this._open(kek2, envelope.dek, aadDekV1(envelope.kdf), "wrong-password"), envelope);
|
|
139
139
|
if (this._kdf === "scrypt") {
|
|
140
140
|
const next = await this._sealEnvelope(password, dek, envelope.secrets, [], envelope.createdAt);
|
|
141
141
|
await this._write(isEnvelopeV2(next) ? { ...next, upgradedAt: Date.now() } : next);
|
|
@@ -144,7 +144,24 @@ var Vault = class {
|
|
|
144
144
|
}
|
|
145
145
|
if (!isStrongScrypt(envelope.kdf)) throw new VaultError(`The vault's scrypt cost (N=${envelope.kdf.N} r=${envelope.kdf.r} p=${envelope.kdf.p}) is below the floor this build accepts.`, "weak-kdf");
|
|
146
146
|
const kek = await this._deriveScrypt(password, fromBase64(envelope.kdf.salt), envelope.kdf);
|
|
147
|
-
return this._open(kek, envelope.dek, aadDekV2(envelope.kdf), "wrong-password");
|
|
147
|
+
return this._proveOwn(await this._open(kek, envelope.dek, aadDekV2(envelope.kdf), "wrong-password"), envelope);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A DEK unwrapped from a dek / passkey box must be the key this vault's secrets are sealed under
|
|
151
|
+
* (audit 2026-11-09 E21): a box spliced in from another vault, under another password or passkey,
|
|
152
|
+
* opens with that password but holds a foreign key. Proven with the DEK check, or — in an envelope
|
|
153
|
+
* written before it — the first secret; an old empty envelope has nothing to prove it with. Returns
|
|
154
|
+
* `dek`; on a mismatch zeroizes it and fails as `wrong-password` (the same failure as tampering).
|
|
155
|
+
*/
|
|
156
|
+
async _proveOwn(dek, envelope) {
|
|
157
|
+
if (envelope.dekCheck === void 0 && Object.keys(envelope.secrets).length === 0) return dek;
|
|
158
|
+
try {
|
|
159
|
+
await this._requireDek(dek, envelope);
|
|
160
|
+
return dek;
|
|
161
|
+
} catch {
|
|
162
|
+
zeroize(dek);
|
|
163
|
+
throw new VaultError("Wrong password.", "wrong-password");
|
|
164
|
+
}
|
|
148
165
|
}
|
|
149
166
|
/**
|
|
150
167
|
* Re-wrap the DEK under a new password (fresh salt, current KDF). Secrets and passkeys are untouched —
|
|
@@ -176,9 +193,13 @@ var Vault = class {
|
|
|
176
193
|
*
|
|
177
194
|
* Passkeys are dropped — their boxes hold the old DEK and cannot be re-wrapped without their PRF
|
|
178
195
|
* output — and must be registered again ({@link Vault.addPasskey} with the new DEK).
|
|
196
|
+
*
|
|
197
|
+
* With `newPassword` this is also the password change: the new DEK is wrapped under `newPassword`
|
|
198
|
+
* (at least {@link VAULT_MIN_PASSWORD_LENGTH} characters), and `password` must be the current one.
|
|
179
199
|
*/
|
|
180
|
-
async rotateVaultKey(password) {
|
|
200
|
+
async rotateVaultKey(password, newPassword) {
|
|
181
201
|
requirePassword(password);
|
|
202
|
+
if (newPassword !== void 0) requirePassword(newPassword, VAULT_MIN_PASSWORD_LENGTH);
|
|
182
203
|
return this._exclusive(async () => {
|
|
183
204
|
const oldDek = await this._unlock(password);
|
|
184
205
|
const dek = this._random(DEK_BYTES);
|
|
@@ -194,7 +215,7 @@ var Vault = class {
|
|
|
194
215
|
zeroize(plaintext);
|
|
195
216
|
}
|
|
196
217
|
}
|
|
197
|
-
const next = await this._sealEnvelope(password, dek, secrets, [], envelope.createdAt);
|
|
218
|
+
const next = await this._sealEnvelope(newPassword ?? password, dek, secrets, [], envelope.createdAt);
|
|
198
219
|
const upgradedAt = isEnvelopeV2(envelope) ? envelope.upgradedAt : void 0;
|
|
199
220
|
await this._write(isEnvelopeV2(next) ? { ...next, ...upgradedAt !== void 0 ? { upgradedAt } : {}, rotatedAt: Date.now() } : next);
|
|
200
221
|
return dek;
|
|
@@ -258,7 +279,7 @@ var Vault = class {
|
|
|
258
279
|
const entry = envelope.passkeys.find((p) => p.id === id);
|
|
259
280
|
if (!entry) throw new VaultError("No such passkey on this vault.", "missing");
|
|
260
281
|
const key = await this._derivePasskeyKek(prf, id);
|
|
261
|
-
return await this._open(key, entry.dek, aadPasskey(id), "wrong-password");
|
|
282
|
+
return await this._proveOwn(await this._open(key, entry.dek, aadPasskey(id), "wrong-password"), envelope);
|
|
262
283
|
} finally {
|
|
263
284
|
zeroize(prf);
|
|
264
285
|
}
|
|
@@ -553,7 +574,11 @@ var Session = class {
|
|
|
553
574
|
return fromBase64(record.key);
|
|
554
575
|
});
|
|
555
576
|
}
|
|
556
|
-
/**
|
|
577
|
+
/**
|
|
578
|
+
* User activity: push the auto-lock out again. No-op when locked. Call it for explicit user
|
|
579
|
+
* gestures only (unlock, send, approve / decline, an action in the wallet's own UI) — not for
|
|
580
|
+
* passive polls, page requests or worker restarts, or the timer never runs out.
|
|
581
|
+
*/
|
|
557
582
|
async touch() {
|
|
558
583
|
return this._serial(async () => {
|
|
559
584
|
const epoch = this._epoch;
|
|
@@ -567,11 +592,25 @@ var Session = class {
|
|
|
567
592
|
get autoLockMs() {
|
|
568
593
|
return this._autoLockMs;
|
|
569
594
|
}
|
|
570
|
-
/**
|
|
595
|
+
/**
|
|
596
|
+
* Set the timer period (0 disables). Never pushes the deadline out — call this at every worker
|
|
597
|
+
* start with the stored setting: it re-arms the alarm at the stored deadline (a restart must not
|
|
598
|
+
* count as activity, audit 2026-11-09 E11), and only brings the deadline forward when the new
|
|
599
|
+
* period ends sooner. A longer period (or 0) applies from the next {@link Session.touch} /
|
|
600
|
+
* {@link Session.unlock}; call `touch()` right after when the user changed it explicitly.
|
|
601
|
+
*/
|
|
571
602
|
async setAutoLockMs(ms) {
|
|
572
603
|
if (!Number.isFinite(ms) || ms < 0) throw new VaultError("Auto-lock must be a non-negative number of milliseconds.", "invalid-input");
|
|
573
604
|
this._autoLockMs = ms;
|
|
574
|
-
|
|
605
|
+
return this._serial(async () => {
|
|
606
|
+
const epoch = this._epoch;
|
|
607
|
+
const record = await this._read();
|
|
608
|
+
if (!record || epoch !== this._epoch) return;
|
|
609
|
+
const cap = this._expiry();
|
|
610
|
+
const expiresAt = cap !== null && (record.expiresAt === null || cap < record.expiresAt) ? cap : record.expiresAt;
|
|
611
|
+
if (expiresAt !== record.expiresAt) await this._storage.set(this._key, { ...record, expiresAt });
|
|
612
|
+
await this._arm(expiresAt);
|
|
613
|
+
});
|
|
575
614
|
}
|
|
576
615
|
onLock(callback) {
|
|
577
616
|
this._onLock.add(callback);
|
|
@@ -841,7 +880,8 @@ var DEFAULT_NETWORKS = Object.freeze([
|
|
|
841
880
|
net(4326, "MegaETH", "ETH", ["https://mainnet.megaeth.com/rpc"]),
|
|
842
881
|
net(130, "Unichain", "ETH", ["https://mainnet.unichain.org", "https://unichain-rpc.publicnode.com"], "https://uniscan.xyz"),
|
|
843
882
|
net(143, "Monad", "MON", ["https://rpc.monad.xyz"]),
|
|
844
|
-
|
|
883
|
+
// Second operator (Tenderly's public gateway, keyless): rpc.plasma.to alone cannot satisfy a two-operator check.
|
|
884
|
+
net(9745, "Plasma", "XPL", ["https://rpc.plasma.to", "https://plasma.gateway.tenderly.co"]),
|
|
845
885
|
net(59144, "Linea", "ETH", ["https://rpc.linea.build", "https://linea-rpc.publicnode.com"], "https://lineascan.build"),
|
|
846
886
|
net(100, "Gnosis", "xDAI", ["https://rpc.gnosischain.com", "https://gnosis-rpc.publicnode.com"], "https://gnosisscan.io"),
|
|
847
887
|
net(146, "Sonic", "S", ["https://rpc.soniclabs.com", "https://sonic-rpc.publicnode.com"], "https://sonicscan.org"),
|
|
@@ -859,7 +899,11 @@ var DEFAULT_NETWORKS = Object.freeze([
|
|
|
859
899
|
net(5e3, "Mantle", "MNT", ["https://rpc.mantle.xyz", "https://mantle-rpc.publicnode.com"], "https://mantlescan.xyz"),
|
|
860
900
|
net(747474, "Katana", "ETH", ["https://rpc.katana.network"], "https://explorer.katanarpc.com"),
|
|
861
901
|
net(2818, "Morph", "ETH", ["https://rpc.morphl2.io", "https://rpc-quicknode.morphl2.io"], "https://explorer.morphl2.io"),
|
|
862
|
-
net(14, "Flare", "FLR", ["https://flare-api.flare.network/ext/C/rpc", "https://flare.rpc.thirdweb.com"], "https://flare-explorer.flare.network")
|
|
902
|
+
net(14, "Flare", "FLR", ["https://flare-api.flare.network/ext/C/rpc", "https://flare.rpc.thirdweb.com"], "https://flare-explorer.flare.network"),
|
|
903
|
+
// Elysium by Kinetiq (chain 99901, HYPE gas; Arbitrum Orbit on HyperEVM) — public RPC and explorer checked 2026-11-10.
|
|
904
|
+
// The explorer value is the link base (<root>/tx/<hash>, <root>/address/<a>), not LI.FI's blockExplorerUrls
|
|
905
|
+
// https://elysium.kinetiq.xyz/explorer/ (the overview page; the bare root is the Elysium site home).
|
|
906
|
+
net(99901, "Elysium", "HYPE", ["https://rpc.elysium.kinetiq.xyz"], "https://elysium.kinetiq.xyz")
|
|
863
907
|
]);
|
|
864
908
|
var chainHex = (caip2) => `0x${Number(caip2.split(":")[1]).toString(16)}`;
|
|
865
909
|
function caip2FromChainId(value) {
|
|
@@ -935,6 +979,17 @@ var NetworksTable = class {
|
|
|
935
979
|
);
|
|
936
980
|
return this._loading;
|
|
937
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Forget the cached table: the next read asks storage again. For a storage whose answer changes under
|
|
984
|
+
* the table — e.g. one that only hands out the user's networks once the wallet is unlocked.
|
|
985
|
+
*/
|
|
986
|
+
reload() {
|
|
987
|
+
return this.serialize(async () => {
|
|
988
|
+
await this._loading?.catch(() => void 0);
|
|
989
|
+
this._custom = null;
|
|
990
|
+
this._loading = null;
|
|
991
|
+
});
|
|
992
|
+
}
|
|
938
993
|
};
|
|
939
994
|
var isSiteRpcUrl = (u) => typeof u === "string" && u.length <= 2048 && isPublicHttpsUrl(u);
|
|
940
995
|
function parseAddChainParams(params, known = isCatalogChain) {
|
|
@@ -1048,6 +1103,21 @@ var PermissionStore = class {
|
|
|
1048
1103
|
return permission;
|
|
1049
1104
|
});
|
|
1050
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Add EVM accounts to an origin's grant (per-site grants): the new ones first, the ones it already
|
|
1108
|
+
* holds stay. The chain stays the origin's own when it has one. Used by the router's connect; `connect`
|
|
1109
|
+
* replaces the list instead.
|
|
1110
|
+
*/
|
|
1111
|
+
grant(origin, accounts, chain) {
|
|
1112
|
+
return this.serialize(async () => {
|
|
1113
|
+
if (accounts.length === 0) throw new Error("Connect at least one account.");
|
|
1114
|
+
const current = await this.get(origin);
|
|
1115
|
+
const merged = [...accounts, ...(current?.accounts ?? []).filter((a) => !accounts.some((x) => x.toLowerCase() === a.toLowerCase()))];
|
|
1116
|
+
const permission = { ...current ?? {}, origin, accounts: merged, chain: current?.chain ?? chain, connectedAt: current?.connectedAt ?? Date.now() };
|
|
1117
|
+
await this.write(origin, permission);
|
|
1118
|
+
return permission;
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1051
1121
|
/** Connect another chain's accounts to the same origin (one "connected sites" list). */
|
|
1052
1122
|
connectChain(origin, key, accounts, defaultChain) {
|
|
1053
1123
|
return this.serialize(async () => {
|
|
@@ -1080,6 +1150,11 @@ var PermissionStore = class {
|
|
|
1080
1150
|
});
|
|
1081
1151
|
}
|
|
1082
1152
|
/**
|
|
1153
|
+
* @deprecated Rewrites every origin's grant to the active account, which shows that account to sites
|
|
1154
|
+
* the user never connected it to (and links accounts across sites). Grants are per site now: the
|
|
1155
|
+
* router shows an origin its granted accounts that are also active, and connecting another account
|
|
1156
|
+
* adds it to that one origin ({@link grant}). Kept for compatibility; nothing in this package calls it.
|
|
1157
|
+
*
|
|
1083
1158
|
* The active EVM account changed: every origin holding an EVM grant now sees `accounts` (MetaMask behaviour).
|
|
1084
1159
|
* An origin connected only on another chain (`accounts: []` — Solana/TON/Tron/BTC/NEAR providers, a WalletConnect
|
|
1085
1160
|
* session without eip155) is skipped: it never passed an EVM connect and must not learn the EVM address (scan F6/F8).
|
|
@@ -1125,6 +1200,179 @@ var PermissionStore = class {
|
|
|
1125
1200
|
};
|
|
1126
1201
|
var sameList = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
1127
1202
|
|
|
1203
|
+
// src/site.ts
|
|
1204
|
+
var MULTI_LABEL_SUFFIXES = /* @__PURE__ */ new Set([
|
|
1205
|
+
// country second-level registries
|
|
1206
|
+
"co.uk",
|
|
1207
|
+
"org.uk",
|
|
1208
|
+
"me.uk",
|
|
1209
|
+
"ltd.uk",
|
|
1210
|
+
"plc.uk",
|
|
1211
|
+
"net.uk",
|
|
1212
|
+
"ac.uk",
|
|
1213
|
+
"gov.uk",
|
|
1214
|
+
"com.au",
|
|
1215
|
+
"net.au",
|
|
1216
|
+
"org.au",
|
|
1217
|
+
"edu.au",
|
|
1218
|
+
"gov.au",
|
|
1219
|
+
"id.au",
|
|
1220
|
+
"co.nz",
|
|
1221
|
+
"org.nz",
|
|
1222
|
+
"net.nz",
|
|
1223
|
+
"co.jp",
|
|
1224
|
+
"ne.jp",
|
|
1225
|
+
"or.jp",
|
|
1226
|
+
"ac.jp",
|
|
1227
|
+
"go.jp",
|
|
1228
|
+
"co.kr",
|
|
1229
|
+
"or.kr",
|
|
1230
|
+
"ne.kr",
|
|
1231
|
+
"com.br",
|
|
1232
|
+
"net.br",
|
|
1233
|
+
"org.br",
|
|
1234
|
+
"com.cn",
|
|
1235
|
+
"net.cn",
|
|
1236
|
+
"org.cn",
|
|
1237
|
+
"com.hk",
|
|
1238
|
+
"com.tw",
|
|
1239
|
+
"com.sg",
|
|
1240
|
+
"com.my",
|
|
1241
|
+
"co.in",
|
|
1242
|
+
"net.in",
|
|
1243
|
+
"org.in",
|
|
1244
|
+
"co.id",
|
|
1245
|
+
"co.il",
|
|
1246
|
+
"co.th",
|
|
1247
|
+
"co.za",
|
|
1248
|
+
"com.mx",
|
|
1249
|
+
"com.ar",
|
|
1250
|
+
"com.co",
|
|
1251
|
+
"com.tr",
|
|
1252
|
+
"com.ua",
|
|
1253
|
+
"com.pl",
|
|
1254
|
+
"com.ru",
|
|
1255
|
+
"com.vn",
|
|
1256
|
+
"com.ph",
|
|
1257
|
+
"com.pk",
|
|
1258
|
+
"com.ng",
|
|
1259
|
+
"com.eg",
|
|
1260
|
+
"com.sa",
|
|
1261
|
+
"co.ke",
|
|
1262
|
+
// hosting platforms: every customer gets its own subdomain
|
|
1263
|
+
"github.io",
|
|
1264
|
+
"gitlab.io",
|
|
1265
|
+
"vercel.app",
|
|
1266
|
+
"now.sh",
|
|
1267
|
+
"pages.dev",
|
|
1268
|
+
"workers.dev",
|
|
1269
|
+
"r2.dev",
|
|
1270
|
+
"netlify.app",
|
|
1271
|
+
"netlify.com",
|
|
1272
|
+
"herokuapp.com",
|
|
1273
|
+
"web.app",
|
|
1274
|
+
"firebaseapp.com",
|
|
1275
|
+
"appspot.com",
|
|
1276
|
+
"azurewebsites.net",
|
|
1277
|
+
"azurestaticapps.net",
|
|
1278
|
+
"cloudfront.net",
|
|
1279
|
+
"amplifyapp.com",
|
|
1280
|
+
"onrender.com",
|
|
1281
|
+
"fly.dev",
|
|
1282
|
+
"railway.app",
|
|
1283
|
+
"up.railway.app",
|
|
1284
|
+
"deno.dev",
|
|
1285
|
+
"glitch.me",
|
|
1286
|
+
"repl.co",
|
|
1287
|
+
"replit.app",
|
|
1288
|
+
"replit.dev",
|
|
1289
|
+
"surge.sh",
|
|
1290
|
+
"webflow.io",
|
|
1291
|
+
"framer.app",
|
|
1292
|
+
"framer.website",
|
|
1293
|
+
"wixsite.com",
|
|
1294
|
+
"blogspot.com",
|
|
1295
|
+
"myshopify.com",
|
|
1296
|
+
"notion.site",
|
|
1297
|
+
"carrd.co",
|
|
1298
|
+
"gitbook.io",
|
|
1299
|
+
"readthedocs.io",
|
|
1300
|
+
"ngrok.io",
|
|
1301
|
+
"ngrok-free.app",
|
|
1302
|
+
"loca.lt",
|
|
1303
|
+
"trycloudflare.com",
|
|
1304
|
+
// ENS / IPFS gateways: every name or CID is its own site
|
|
1305
|
+
"eth.limo",
|
|
1306
|
+
"eth.link",
|
|
1307
|
+
"ipfs.dweb.link",
|
|
1308
|
+
"ipns.dweb.link",
|
|
1309
|
+
"ipfs.w3s.link",
|
|
1310
|
+
"ipfs.cf-ipfs.com"
|
|
1311
|
+
]);
|
|
1312
|
+
var IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
1313
|
+
function siteOf(origin) {
|
|
1314
|
+
let host;
|
|
1315
|
+
try {
|
|
1316
|
+
const url = new URL(origin);
|
|
1317
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return origin;
|
|
1318
|
+
host = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
1319
|
+
} catch {
|
|
1320
|
+
return origin;
|
|
1321
|
+
}
|
|
1322
|
+
if (!host || host.startsWith("[") || host.includes(":") || IPV4.test(host)) return host || origin;
|
|
1323
|
+
const labels = host.split(".");
|
|
1324
|
+
if (labels.length <= 2) return host;
|
|
1325
|
+
for (const size of [3, 2]) {
|
|
1326
|
+
if (labels.length <= size) continue;
|
|
1327
|
+
if (MULTI_LABEL_SUFFIXES.has(labels.slice(-size).join("."))) return labels.slice(-(size + 1)).join(".");
|
|
1328
|
+
}
|
|
1329
|
+
return labels.slice(-2).join(".");
|
|
1330
|
+
}
|
|
1331
|
+
var TokenBucket = class {
|
|
1332
|
+
constructor(options) {
|
|
1333
|
+
this.options = options;
|
|
1334
|
+
}
|
|
1335
|
+
options;
|
|
1336
|
+
buckets = /* @__PURE__ */ new Map();
|
|
1337
|
+
take(key, cost = 1) {
|
|
1338
|
+
const now = (this.options.now ?? Date.now)();
|
|
1339
|
+
const b = this.refill(key, now);
|
|
1340
|
+
if (b.tokens < cost) return false;
|
|
1341
|
+
b.tokens -= cost;
|
|
1342
|
+
return true;
|
|
1343
|
+
}
|
|
1344
|
+
/** Tokens `key` holds right now (full for an unknown key). */
|
|
1345
|
+
available(key) {
|
|
1346
|
+
return this.refill(key, (this.options.now ?? Date.now)()).tokens;
|
|
1347
|
+
}
|
|
1348
|
+
refill(key, now) {
|
|
1349
|
+
let b = this.buckets.get(key);
|
|
1350
|
+
if (!b) {
|
|
1351
|
+
if (this.buckets.size >= (this.options.maxKeys ?? 2e3)) this.prune(now);
|
|
1352
|
+
b = { tokens: this.options.capacity, at: now };
|
|
1353
|
+
this.buckets.set(key, b);
|
|
1354
|
+
return b;
|
|
1355
|
+
}
|
|
1356
|
+
const elapsed = Math.max(0, now - b.at) / 1e3;
|
|
1357
|
+
b.tokens = Math.min(this.options.capacity, b.tokens + elapsed * this.options.refillPerSecond);
|
|
1358
|
+
b.at = now;
|
|
1359
|
+
return b;
|
|
1360
|
+
}
|
|
1361
|
+
/** Drop buckets that are full again (they carry no state); the oldest half when every one is in use. */
|
|
1362
|
+
prune(now) {
|
|
1363
|
+
for (const [k, b] of this.buckets) {
|
|
1364
|
+
if (b.tokens + Math.max(0, now - b.at) / 1e3 * this.options.refillPerSecond >= this.options.capacity) this.buckets.delete(k);
|
|
1365
|
+
}
|
|
1366
|
+
if (this.buckets.size >= (this.options.maxKeys ?? 2e3)) {
|
|
1367
|
+
let drop = Math.ceil(this.buckets.size / 2);
|
|
1368
|
+
for (const k of this.buckets.keys()) {
|
|
1369
|
+
if (drop-- <= 0) break;
|
|
1370
|
+
this.buckets.delete(k);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1128
1376
|
// src/approvals.ts
|
|
1129
1377
|
var ReviewDeclined = class extends Error {
|
|
1130
1378
|
code = RELAY_ERRORS.userRejected;
|
|
@@ -1149,7 +1397,11 @@ var OriginRefused = class extends Error {
|
|
|
1149
1397
|
}
|
|
1150
1398
|
};
|
|
1151
1399
|
var APPROVALS_MAX_PENDING_PER_ORIGIN = 3;
|
|
1152
|
-
var
|
|
1400
|
+
var APPROVALS_MAX_PENDING_PER_SITE = 5;
|
|
1401
|
+
var APPROVALS_MAX_PENDING_TOTAL = 20;
|
|
1402
|
+
var FOLLOW_UP_CREDITS_MAX = 8;
|
|
1403
|
+
var FOLLOW_UP_KEYS_MAX = 256;
|
|
1404
|
+
var ApprovalQueue = class _ApprovalQueue {
|
|
1153
1405
|
constructor(window, newId = () => crypto.randomUUID(), options = {}) {
|
|
1154
1406
|
this.window = window;
|
|
1155
1407
|
this.newId = newId;
|
|
@@ -1157,6 +1409,8 @@ var ApprovalQueue = class {
|
|
|
1157
1409
|
window.onClosed((id) => {
|
|
1158
1410
|
if (this._current?.request.id === id) this.decline(id);
|
|
1159
1411
|
});
|
|
1412
|
+
const rate = options.openRate === false ? null : options.openRate ?? {};
|
|
1413
|
+
this._rate = rate ? new TokenBucket({ capacity: rate.burst ?? 10, refillPerSecond: rate.refillPerSecond ?? 1 / 6, ...options.now ? { now: options.now } : {} }) : null;
|
|
1160
1414
|
}
|
|
1161
1415
|
window;
|
|
1162
1416
|
newId;
|
|
@@ -1165,8 +1419,62 @@ var ApprovalQueue = class {
|
|
|
1165
1419
|
_waiting = [];
|
|
1166
1420
|
_listeners = /* @__PURE__ */ new Set();
|
|
1167
1421
|
_via = /* @__PURE__ */ new Map();
|
|
1422
|
+
_rate;
|
|
1423
|
+
/** Per site: cooldown end and strikes (how many times the bucket ran dry recently). */
|
|
1424
|
+
_cooldown = /* @__PURE__ */ new Map();
|
|
1425
|
+
/** Per origin + kind + chain: approvals that have not been followed up (see {@link ApprovalRequestOptions.followUp}). */
|
|
1426
|
+
_approved = /* @__PURE__ */ new Map();
|
|
1427
|
+
site(origin) {
|
|
1428
|
+
try {
|
|
1429
|
+
return (this.options.siteOf ?? siteOf)(origin);
|
|
1430
|
+
} catch {
|
|
1431
|
+
return origin;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
get all() {
|
|
1435
|
+
return [...this._current ? [this._current] : [], ...this._waiting];
|
|
1436
|
+
}
|
|
1437
|
+
/** The per-site rate: a reason to refuse, or null (and one token spent). */
|
|
1438
|
+
rateRefusal(site) {
|
|
1439
|
+
if (!this._rate) return null;
|
|
1440
|
+
const rate = this.options.openRate || {};
|
|
1441
|
+
const now = (this.options.now ?? Date.now)();
|
|
1442
|
+
const max = rate.maxBackoffMs ?? 6e5;
|
|
1443
|
+
const c = this._cooldown.get(site);
|
|
1444
|
+
if (c && now < c.until) return `This site opened too many requests; try again in ${Math.ceil((c.until - now) / 1e3)} s.`;
|
|
1445
|
+
if (this._rate.take(site)) return null;
|
|
1446
|
+
const strikes = c && now - c.last < max ? c.strikes + 1 : 1;
|
|
1447
|
+
const wait = Math.min(max, (rate.backoffMs ?? 3e4) * 2 ** (strikes - 1));
|
|
1448
|
+
this._cooldown.set(site, { until: now + wait, strikes, last: now });
|
|
1449
|
+
if (this._cooldown.size > 1e3) {
|
|
1450
|
+
for (const [k, v] of this._cooldown) if (now - v.last >= max) this._cooldown.delete(k);
|
|
1451
|
+
}
|
|
1452
|
+
return `This site opened too many requests; try again in ${Math.ceil(wait / 1e3)} s.`;
|
|
1453
|
+
}
|
|
1454
|
+
static followUpKey(r) {
|
|
1455
|
+
return `${r.origin}
|
|
1456
|
+
${r.kind}
|
|
1457
|
+
${r.chain}`;
|
|
1458
|
+
}
|
|
1459
|
+
/** The user approved `r`: it may be followed up once. */
|
|
1460
|
+
approved(r) {
|
|
1461
|
+
const key = _ApprovalQueue.followUpKey(r);
|
|
1462
|
+
const n = Math.min((this._approved.get(key) ?? 0) + 1, FOLLOW_UP_CREDITS_MAX);
|
|
1463
|
+
this._approved.delete(key);
|
|
1464
|
+
this._approved.set(key, n);
|
|
1465
|
+
if (this._approved.size > FOLLOW_UP_KEYS_MAX) this._approved.delete(this._approved.keys().next().value);
|
|
1466
|
+
}
|
|
1467
|
+
/** Spends one approval of the same origin, kind and chain; false when there is none. */
|
|
1468
|
+
spendApproval(r) {
|
|
1469
|
+
const key = _ApprovalQueue.followUpKey(r);
|
|
1470
|
+
const n = this._approved.get(key) ?? 0;
|
|
1471
|
+
if (n <= 0) return false;
|
|
1472
|
+
if (n === 1) this._approved.delete(key);
|
|
1473
|
+
else this._approved.set(key, n - 1);
|
|
1474
|
+
return true;
|
|
1475
|
+
}
|
|
1168
1476
|
/** Enqueue and wait for the user's answer. Refuses (never queues) past the per-origin limits. */
|
|
1169
|
-
request(input) {
|
|
1477
|
+
request(input, opts = {}) {
|
|
1170
1478
|
let refused;
|
|
1171
1479
|
try {
|
|
1172
1480
|
refused = this.options.refuse?.(input.origin);
|
|
@@ -1174,19 +1482,41 @@ var ApprovalQueue = class {
|
|
|
1174
1482
|
refused = `The wallet could not check this site (${e instanceof Error ? e.message : String(e)}).`;
|
|
1175
1483
|
}
|
|
1176
1484
|
if (refused) return Promise.reject(new OriginRefused(refused));
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1485
|
+
let exempt = false;
|
|
1486
|
+
try {
|
|
1487
|
+
exempt = this.options.exempt?.(input.origin) === true;
|
|
1488
|
+
} catch {
|
|
1489
|
+
exempt = false;
|
|
1180
1490
|
}
|
|
1181
|
-
const
|
|
1182
|
-
if (
|
|
1183
|
-
|
|
1491
|
+
const followUp = opts.followUp === true && this.spendApproval(input);
|
|
1492
|
+
if (!exempt && !followUp) {
|
|
1493
|
+
const all = this.all;
|
|
1494
|
+
const sameOrigin = all.filter((p) => p.request.origin === input.origin);
|
|
1495
|
+
if (input.kind === "connect" && sameOrigin.some((p) => p.request.kind === "connect")) {
|
|
1496
|
+
return Promise.reject(new QueueLimitError(RELAY_ERRORS.resourceUnavailable, "A connect request from this site is already waiting for your answer."));
|
|
1497
|
+
}
|
|
1498
|
+
const max = this.options.maxPendingPerOrigin ?? APPROVALS_MAX_PENDING_PER_ORIGIN;
|
|
1499
|
+
if (sameOrigin.length >= max) {
|
|
1500
|
+
return Promise.reject(new QueueLimitError(RELAY_ERRORS.limitExceeded, `This site already has ${max} requests waiting for you; answer or close them first.`));
|
|
1501
|
+
}
|
|
1502
|
+
const site = this.site(input.origin);
|
|
1503
|
+
const maxSite = this.options.maxPendingPerSite ?? APPROVALS_MAX_PENDING_PER_SITE;
|
|
1504
|
+
if (all.filter((p) => this.site(p.request.origin) === site).length >= maxSite) {
|
|
1505
|
+
return Promise.reject(new QueueLimitError(RELAY_ERRORS.limitExceeded, `This site already has ${maxSite} requests waiting for you across its subdomains; answer or close them first.`));
|
|
1506
|
+
}
|
|
1507
|
+
const maxTotal = this.options.maxPendingTotal ?? APPROVALS_MAX_PENDING_TOTAL;
|
|
1508
|
+
if (all.filter((p) => !this.isExempt(p.request.origin)).length >= maxTotal) {
|
|
1509
|
+
return Promise.reject(new QueueLimitError(RELAY_ERRORS.limitExceeded, `The wallet already has ${maxTotal} requests waiting for you; answer or close them first.`));
|
|
1510
|
+
}
|
|
1511
|
+
const slow = this.rateRefusal(site);
|
|
1512
|
+
if (slow) return Promise.reject(new QueueLimitError(RELAY_ERRORS.limitExceeded, slow));
|
|
1184
1513
|
}
|
|
1185
1514
|
return new Promise((resolve, reject) => {
|
|
1186
1515
|
const warnings = this.options.annotate?.(input.origin) ?? [];
|
|
1187
1516
|
const via = this._via.get(input.origin);
|
|
1188
1517
|
const pending = { request: { ...input, id: this.newId(), createdAt: Date.now(), ...warnings.length ? { originWarnings: warnings } : {}, ...via ? { via } : {} }, resolve, reject };
|
|
1189
|
-
this._waiting.
|
|
1518
|
+
if (followUp) this._waiting.unshift(pending);
|
|
1519
|
+
else this._waiting.push(pending);
|
|
1190
1520
|
void this.next();
|
|
1191
1521
|
});
|
|
1192
1522
|
}
|
|
@@ -1199,9 +1529,37 @@ var ApprovalQueue = class {
|
|
|
1199
1529
|
viaFor(origin) {
|
|
1200
1530
|
return this._via.get(origin);
|
|
1201
1531
|
}
|
|
1532
|
+
isExempt(origin) {
|
|
1533
|
+
try {
|
|
1534
|
+
return this.options.exempt?.(origin) === true;
|
|
1535
|
+
} catch {
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1202
1539
|
/** Requests waiting for `origin` (current + queued). */
|
|
1203
1540
|
pendingFor(origin) {
|
|
1204
|
-
return
|
|
1541
|
+
return this.all.filter((p) => p.request.origin === origin).length;
|
|
1542
|
+
}
|
|
1543
|
+
/** Requests waiting for any origin of `site` (current + queued). */
|
|
1544
|
+
pendingForSite(site) {
|
|
1545
|
+
return this.all.filter((p) => this.site(p.request.origin) === site).length;
|
|
1546
|
+
}
|
|
1547
|
+
/** The site (registrable domain) the queue's limits put `origin` under. */
|
|
1548
|
+
siteFor(origin) {
|
|
1549
|
+
return this.site(origin);
|
|
1550
|
+
}
|
|
1551
|
+
/** Decline everything waiting for `origin` (its last page closed). Returns how many requests were declined. */
|
|
1552
|
+
declineOrigin(origin, error = new ReviewDeclined("The page was closed \u2014 nothing was signed.")) {
|
|
1553
|
+
return this.declineWhere((p) => p.request.origin === origin, error);
|
|
1554
|
+
}
|
|
1555
|
+
/** "Decline all from this site": everything waiting for any origin of `site` (see {@link siteFor}). */
|
|
1556
|
+
declineSite(site, error = new ReviewDeclined()) {
|
|
1557
|
+
return this.declineWhere((p) => this.site(p.request.origin) === site, error);
|
|
1558
|
+
}
|
|
1559
|
+
declineWhere(match, error) {
|
|
1560
|
+
const ids = [...this._waiting.filter(match), ...this._current && match(this._current) ? [this._current] : []].map((p) => p.request.id);
|
|
1561
|
+
for (const id of ids) this.decline(id, error);
|
|
1562
|
+
return ids.length;
|
|
1205
1563
|
}
|
|
1206
1564
|
/** The request currently shown (the window asks for it by id on load). */
|
|
1207
1565
|
get current() {
|
|
@@ -1211,7 +1569,10 @@ var ApprovalQueue = class {
|
|
|
1211
1569
|
return this._waiting.length;
|
|
1212
1570
|
}
|
|
1213
1571
|
resolve(requestId, value) {
|
|
1214
|
-
return this.settle(requestId, (p) =>
|
|
1572
|
+
return this.settle(requestId, (p) => {
|
|
1573
|
+
this.approved(p.request);
|
|
1574
|
+
p.resolve(value);
|
|
1575
|
+
});
|
|
1215
1576
|
}
|
|
1216
1577
|
decline(requestId, error = new ReviewDeclined()) {
|
|
1217
1578
|
return this.settle(requestId, (p) => p.reject(error));
|
|
@@ -1219,6 +1580,7 @@ var ApprovalQueue = class {
|
|
|
1219
1580
|
/** Lock, disconnect, shutdown: everything pending is declined. */
|
|
1220
1581
|
declineAll(error = new ReviewDeclined("The wallet was locked.")) {
|
|
1221
1582
|
const all = [...this._current ? [this._current] : [], ...this._waiting.splice(0)];
|
|
1583
|
+
this._approved.clear();
|
|
1222
1584
|
for (const p of all) p.reject(error);
|
|
1223
1585
|
if (this._current) void this.window.close(this._current.request.id);
|
|
1224
1586
|
this._current = null;
|
|
@@ -1350,6 +1712,7 @@ var READ_METHODS = /* @__PURE__ */ new Set([
|
|
|
1350
1712
|
"eth_getTransactionReceipt",
|
|
1351
1713
|
"eth_maxPriorityFeePerGas"
|
|
1352
1714
|
]);
|
|
1715
|
+
var GETLOGS_MAX_SPAN_UNCONNECTED = 1e3;
|
|
1353
1716
|
var SIGNING_METHODS = /* @__PURE__ */ new Set(["personal_sign", "eth_signTypedData_v4", "eth_signTypedData_v3", "eth_signTypedData", "eth_sendTransaction"]);
|
|
1354
1717
|
var RequestRouter = class {
|
|
1355
1718
|
constructor(deps) {
|
|
@@ -1379,8 +1742,10 @@ var RequestRouter = class {
|
|
|
1379
1742
|
return this.visibleAccounts(permission);
|
|
1380
1743
|
case "eth_requestAccounts":
|
|
1381
1744
|
return this.connect(origin, network);
|
|
1382
|
-
case "wallet_getPermissions":
|
|
1383
|
-
|
|
1745
|
+
case "wallet_getPermissions": {
|
|
1746
|
+
const visible = await this.visibleAccounts(permission);
|
|
1747
|
+
return visible.length > 0 ? [permissionDescriptor(origin, visible)] : [];
|
|
1748
|
+
}
|
|
1384
1749
|
case "wallet_requestPermissions": {
|
|
1385
1750
|
const accounts = await this.connect(origin, network);
|
|
1386
1751
|
return [permissionDescriptor(origin, accounts)];
|
|
@@ -1421,11 +1786,20 @@ var RequestRouter = class {
|
|
|
1421
1786
|
case "eth_signTransaction":
|
|
1422
1787
|
throw new RouterError(RELAY_ERRORS.unsupportedMethod, `${method} (blind signing) is never offered by this wallet.`);
|
|
1423
1788
|
}
|
|
1424
|
-
if (READ_METHODS.has(method))
|
|
1789
|
+
if (READ_METHODS.has(method)) {
|
|
1790
|
+
if (method === "eth_getLogs" && !(permission && permission.accounts.length > 0)) await this.boundLogs(network, params);
|
|
1791
|
+
return this.deps.rpc(network, method, params);
|
|
1792
|
+
}
|
|
1425
1793
|
if (SIGNING_METHODS.has(method)) {
|
|
1426
1794
|
if (!this.deps.sign) throw new RouterError(RELAY_ERRORS.unsupportedMethod, `${method} is not available yet.`);
|
|
1427
|
-
if (!permission) throw new RouterError(RELAY_ERRORS.unauthorized, "Connect the wallet first (eth_requestAccounts).");
|
|
1428
|
-
|
|
1795
|
+
if (!permission || permission.accounts.length === 0) throw new RouterError(RELAY_ERRORS.unauthorized, "Connect the wallet first (eth_requestAccounts).");
|
|
1796
|
+
const visible = await this.visibleAccounts(permission);
|
|
1797
|
+
const named = namedAccount(method, params);
|
|
1798
|
+
const inactive = named !== void 0 && permission.accounts.some((a) => a.toLowerCase() === named.toLowerCase()) && !visible.some((a) => a.toLowerCase() === named.toLowerCase());
|
|
1799
|
+
if (visible.length === 0 || inactive) {
|
|
1800
|
+
throw new RouterError(RELAY_ERRORS.unauthorized, "The account this site asks for is not the wallet's active account (or the wallet is locked). Switch to it in the wallet, or connect the active account (eth_requestAccounts).");
|
|
1801
|
+
}
|
|
1802
|
+
return this.deps.sign({ origin, network, method, params, accounts: visible });
|
|
1429
1803
|
}
|
|
1430
1804
|
throw new RouterError(RELAY_ERRORS.unsupportedMethod, `This wallet does not support ${method}.`);
|
|
1431
1805
|
}
|
|
@@ -1435,9 +1809,31 @@ var RequestRouter = class {
|
|
|
1435
1809
|
const available = await this.deps.accounts();
|
|
1436
1810
|
return permission.accounts.filter((a) => available.includes(a));
|
|
1437
1811
|
}
|
|
1812
|
+
/** An unconnected origin's `eth_getLogs`: a block hash, or a range of at most {@link GETLOGS_MAX_SPAN_UNCONNECTED} blocks. */
|
|
1813
|
+
async boundLogs(network, params) {
|
|
1814
|
+
const filter = params[0] ?? {};
|
|
1815
|
+
if (typeof filter !== "object" || filter === null) throw new RouterError(RELAY_ERRORS.invalidParams, "eth_getLogs expects a filter object.");
|
|
1816
|
+
if (filter.blockHash !== void 0 && filter.blockHash !== null) return;
|
|
1817
|
+
let head;
|
|
1818
|
+
const block = async (tag) => {
|
|
1819
|
+
if (tag === void 0 || tag === null || tag === "latest" || tag === "pending" || tag === "safe" || tag === "finalized") {
|
|
1820
|
+
head ??= BigInt(String(await this.deps.rpc(network, "eth_blockNumber", [])));
|
|
1821
|
+
return head;
|
|
1822
|
+
}
|
|
1823
|
+
if (tag === "earliest") return 0n;
|
|
1824
|
+
if (typeof tag === "string" && /^0x[0-9a-fA-F]{1,16}$/.test(tag)) return BigInt(tag);
|
|
1825
|
+
throw new RouterError(RELAY_ERRORS.invalidParams, "eth_getLogs: fromBlock / toBlock must be a block tag or a hex block number.");
|
|
1826
|
+
};
|
|
1827
|
+
const from = await block(filter.fromBlock);
|
|
1828
|
+
const to = await block(filter.toBlock);
|
|
1829
|
+
if (to - from > BigInt(GETLOGS_MAX_SPAN_UNCONNECTED)) {
|
|
1830
|
+
throw new RouterError(RELAY_ERRORS.limitExceeded, `eth_getLogs over more than ${GETLOGS_MAX_SPAN_UNCONNECTED} blocks is answered for connected sites only; narrow the range or connect the wallet.`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1438
1833
|
/**
|
|
1439
1834
|
* Connected and unlocked → the accounts; otherwise the approval window (which
|
|
1440
|
-
* unlocks first when needed) picks them
|
|
1835
|
+
* unlocks first when needed) picks them, and they are ADDED to this origin's
|
|
1836
|
+
* grant (other origins are untouched). Emits `connect` + `accountsChanged`.
|
|
1441
1837
|
*/
|
|
1442
1838
|
async connect(origin, network) {
|
|
1443
1839
|
const existing = await this.visibleAccounts(await this.deps.permissions.get(origin));
|
|
@@ -1446,7 +1842,7 @@ var RequestRouter = class {
|
|
|
1446
1842
|
const available = await this.deps.accounts();
|
|
1447
1843
|
const accounts = Array.isArray(chosen) ? chosen.filter((a) => typeof a === "string" && available.includes(a)) : [];
|
|
1448
1844
|
if (accounts.length === 0) throw new ReviewDeclined("No account was selected.");
|
|
1449
|
-
await this.deps.permissions.
|
|
1845
|
+
await this.deps.permissions.grant(origin, accounts, network.caip2);
|
|
1450
1846
|
this.deps.emit(origin, "connect", { chainId: chainHex(network.caip2) });
|
|
1451
1847
|
this.deps.emit(origin, "accountsChanged", accounts);
|
|
1452
1848
|
return accounts;
|
|
@@ -1466,6 +1862,12 @@ var RequestRouter = class {
|
|
|
1466
1862
|
this.deps.emit(origin, "chainChanged", chainHex(to.caip2));
|
|
1467
1863
|
}
|
|
1468
1864
|
};
|
|
1865
|
+
function namedAccount(method, params) {
|
|
1866
|
+
const addr = (x) => typeof x === "string" && /^0x[0-9a-fA-F]{40}$/.test(x) ? x : void 0;
|
|
1867
|
+
if (method === "personal_sign") return addr(params[1]) ?? addr(params[0]);
|
|
1868
|
+
if (method === "eth_sendTransaction") return addr(params[0]?.from);
|
|
1869
|
+
return addr(params[0]);
|
|
1870
|
+
}
|
|
1469
1871
|
var permissionDescriptor = (origin, accounts) => ({
|
|
1470
1872
|
id: crypto.randomUUID(),
|
|
1471
1873
|
parentCapability: "eth_accounts",
|
|
@@ -1474,7 +1876,60 @@ var permissionDescriptor = (origin, accounts) => ({
|
|
|
1474
1876
|
date: Date.now()
|
|
1475
1877
|
});
|
|
1476
1878
|
|
|
1879
|
+
// src/personal-sign.ts
|
|
1880
|
+
var HEX = /^0x([0-9a-fA-F]{2})*$/;
|
|
1881
|
+
var BLIND_HASH_MAX_CHARS = 66;
|
|
1882
|
+
var hexBytes = (value) => Uint8Array.from(value.slice(2).match(/../g) ?? [], (b) => Number.parseInt(b, 16));
|
|
1883
|
+
function codePoint(s, i) {
|
|
1884
|
+
const c = s.charCodeAt(i);
|
|
1885
|
+
if (c < 55296 || c > 57343) return c;
|
|
1886
|
+
const lo = c <= 56319 && i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
|
|
1887
|
+
return lo >= 56320 && lo <= 57343 ? 65536 + (c - 55296 << 10) + (lo - 56320) : 65533;
|
|
1888
|
+
}
|
|
1889
|
+
function utf8Length(s) {
|
|
1890
|
+
let n = 0;
|
|
1891
|
+
for (let i = 0; i < s.length; i++) {
|
|
1892
|
+
const c = codePoint(s, i);
|
|
1893
|
+
if (c > 65535) {
|
|
1894
|
+
n += 4;
|
|
1895
|
+
i++;
|
|
1896
|
+
} else n += c < 128 ? 1 : c < 2048 ? 2 : 3;
|
|
1897
|
+
}
|
|
1898
|
+
return n;
|
|
1899
|
+
}
|
|
1900
|
+
function utf8Bytes(s) {
|
|
1901
|
+
const out = new Uint8Array(utf8Length(s));
|
|
1902
|
+
let o = 0;
|
|
1903
|
+
for (let i = 0; i < s.length; i++) {
|
|
1904
|
+
const c = codePoint(s, i);
|
|
1905
|
+
if (c < 128) out[o++] = c;
|
|
1906
|
+
else if (c < 2048) {
|
|
1907
|
+
out[o++] = 192 | c >> 6;
|
|
1908
|
+
out[o++] = 128 | c & 63;
|
|
1909
|
+
} else if (c < 65536) {
|
|
1910
|
+
out[o++] = 224 | c >> 12;
|
|
1911
|
+
out[o++] = 128 | c >> 6 & 63;
|
|
1912
|
+
out[o++] = 128 | c & 63;
|
|
1913
|
+
} else {
|
|
1914
|
+
out[o++] = 240 | c >> 18;
|
|
1915
|
+
out[o++] = 128 | c >> 12 & 63;
|
|
1916
|
+
out[o++] = 128 | c >> 6 & 63;
|
|
1917
|
+
out[o++] = 128 | c & 63;
|
|
1918
|
+
i++;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return out;
|
|
1922
|
+
}
|
|
1923
|
+
function personalSignInput(message) {
|
|
1924
|
+
return HEX.test(message) ? hexBytes(message) : message;
|
|
1925
|
+
}
|
|
1926
|
+
function personalSignBytes(message) {
|
|
1927
|
+
const input = personalSignInput(message);
|
|
1928
|
+
return typeof input === "string" ? utf8Bytes(input) : input;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1477
1931
|
// src/siwe.ts
|
|
1932
|
+
var SIWE_FORBIDDEN_CHARS = /[\u061c\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/;
|
|
1478
1933
|
var HEADER_LINE = /^(?<domain>[^\n]+?) wants you to sign in with your Ethereum account:(?:\n|$)/;
|
|
1479
1934
|
var ADDRESS_LINE = /^0x[0-9a-fA-F]{40}$/;
|
|
1480
1935
|
var FIELD = /^([A-Za-z ]+): (.*)$/;
|
|
@@ -1578,12 +2033,20 @@ function siweProblems(m, ctx) {
|
|
|
1578
2033
|
}
|
|
1579
2034
|
return out;
|
|
1580
2035
|
}
|
|
2036
|
+
function siweCrossSite(m, ctx) {
|
|
2037
|
+
const site = originWhere(ctx.origin) ?? (ctx.via?.verify === "VALID" && !ctx.via.scam ? whereOf(ctx.via.url) : null);
|
|
2038
|
+
if (!site) return null;
|
|
2039
|
+
const domain = whereOf(m.domain);
|
|
2040
|
+
return !domain || domain.host !== site.host ? { domain: m.domain, origin: `${site.scheme}://${site.host}` } : null;
|
|
2041
|
+
}
|
|
1581
2042
|
function describeSiwe(text, ctx) {
|
|
1582
2043
|
const p = parseLoose(text);
|
|
1583
2044
|
if (!p) return null;
|
|
1584
2045
|
const problems = siweProblems(p.message, ctx);
|
|
1585
|
-
|
|
1586
|
-
|
|
2046
|
+
const cross = siweCrossSite(p.message, ctx);
|
|
2047
|
+
const crossSite = cross ? { crossSite: cross } : {};
|
|
2048
|
+
if (p.missing.length === 0) return { message: p.message, problems, ...crossSite };
|
|
2049
|
+
return { message: p.message, problems: [`This is a malformed sign-in message (EIP-4361): ${p.missing.join(", ")} missing or unreadable \u2014 check the site before signing.`, ...problems], malformed: true, ...crossSite };
|
|
1587
2050
|
}
|
|
1588
2051
|
|
|
1589
2052
|
// src/signing.ts
|
|
@@ -1591,18 +2054,30 @@ var safeNonce = (n) => {
|
|
|
1591
2054
|
if (n < 0n || n > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`The RPC returned an impossible nonce (${n}).`);
|
|
1592
2055
|
return Number(n.toString());
|
|
1593
2056
|
};
|
|
2057
|
+
var MAX_FEE_REASKS = 3;
|
|
1594
2058
|
var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
1595
|
-
var HEX = /^0x([0-9a-fA-F]{2})*$/;
|
|
1596
2059
|
var lc = (s) => s.toLowerCase();
|
|
2060
|
+
var INVISIBLE = /[\p{Cf}\u2028\u2029\u115f\u1160\u3164\uffa0\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/gu;
|
|
2061
|
+
function revealHidden(text) {
|
|
2062
|
+
let hidden = false;
|
|
2063
|
+
const out = text.replace(INVISIBLE, (c) => {
|
|
2064
|
+
hidden = true;
|
|
2065
|
+
return `\u27E8U+${c.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}\u27E9`;
|
|
2066
|
+
});
|
|
2067
|
+
return { text: out, hidden };
|
|
2068
|
+
}
|
|
1597
2069
|
function messageText(value) {
|
|
1598
|
-
|
|
1599
|
-
|
|
2070
|
+
return shownMessage(value).text;
|
|
2071
|
+
}
|
|
2072
|
+
function shownMessage(value) {
|
|
2073
|
+
if (typeof value !== "string") return { text: "", hidden: false };
|
|
2074
|
+
if (!HEX.test(value)) return revealHidden(value);
|
|
1600
2075
|
try {
|
|
1601
2076
|
const bytes = Uint8Array.from(value.slice(2).match(/../g) ?? [], (b) => Number.parseInt(b, 16));
|
|
1602
2077
|
const s = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1603
|
-
return /^[\x09\x0a\x0d\x20-\x7e
|
|
2078
|
+
return /^[\x09\x0a\x0d\x20-\x7e\u00a0-\uffff]*$/.test(s) ? revealHidden(s) : { text: value, hidden: false };
|
|
1604
2079
|
} catch {
|
|
1605
|
-
return value;
|
|
2080
|
+
return { text: value, hidden: false };
|
|
1606
2081
|
}
|
|
1607
2082
|
}
|
|
1608
2083
|
function decodedMessage(value) {
|
|
@@ -1626,11 +2101,16 @@ function classifySignRequest(input) {
|
|
|
1626
2101
|
if (ours(msg) && !ours(addr)) [msg, addr] = [addr, msg];
|
|
1627
2102
|
if (!ours(addr)) return err(RELAY_ERRORS.unauthorized, "personal_sign for an address this wallet does not hold.");
|
|
1628
2103
|
if (typeof msg !== "string") return err(RELAY_ERRORS.invalidParams, "personal_sign expects a message string.");
|
|
1629
|
-
const
|
|
2104
|
+
const thirtyTwo = msg.length <= BLIND_HASH_MAX_CHARS && personalSignBytes(msg).length === 32;
|
|
2105
|
+
const blindHashForm = !thirtyTwo ? void 0 : HEX.test(msg) ? "hex" : "text";
|
|
2106
|
+
if (blindHashForm === "hex" && !input.allowBlindHash) return err(RELAY_ERRORS.unsupportedMethod, 'This site asks you to sign a raw 32-byte hash (blind signing). It can be a swap order or a transfer (e.g. CoW Protocol) paying someone else, so this wallet refuses it. "Allow blind hash signatures" in Options turns on a review for such requests.');
|
|
2107
|
+
const shown = shownMessage(msg);
|
|
2108
|
+
const text = shown.text;
|
|
1630
2109
|
const signed = decodedMessage(msg) ?? text;
|
|
1631
2110
|
const siwe = input.origin ? describeSiwe(signed, { origin: input.origin, account: checksum(addr), chainId: input.network.chainId, ...input.via ? { via: input.via } : {}, ...input.now !== void 0 ? { now: input.now } : {} }) : null;
|
|
1632
2111
|
if (siwe && HIDDEN_CONTROL.test(signed)) return err(RELAY_ERRORS.invalidParams, "This sign-in message contains hidden control characters \u2014 refused.");
|
|
1633
|
-
|
|
2112
|
+
if (siwe && SIWE_FORBIDDEN_CHARS.test(signed)) return err(RELAY_ERRORS.invalidParams, "This sign-in message contains invisible or text-direction characters \u2014 refused.");
|
|
2113
|
+
return { kind: "sign-message", method, account: checksum(addr), text, raw: msg, ...siwe ? { siwe } : {}, ...shown.hidden ? { hiddenChars: true } : {}, ...blindHashForm ? { blindHash: true, blindHashForm } : {} };
|
|
1634
2114
|
}
|
|
1635
2115
|
if (method === "eth_signTypedData_v4" || method === "eth_signTypedData_v3" || method === "eth_signTypedData") {
|
|
1636
2116
|
const [addr, data] = params;
|
|
@@ -1648,7 +2128,8 @@ function classifySignRequest(input) {
|
|
|
1648
2128
|
if (cid !== void 0 && cid !== null && Number(cid) !== input.network.chainId) {
|
|
1649
2129
|
return err(RELAY_ERRORS.chainDisconnected, `Typed data is for chain ${String(cid)}; this site is on ${input.network.name} (${input.network.chainId}).`);
|
|
1650
2130
|
}
|
|
1651
|
-
|
|
2131
|
+
const shown = revealHidden(JSON.stringify(parsed, bigintSafe, 2));
|
|
2132
|
+
return { kind: "sign-typed-data", method, account: checksum(addr), text: shown.text, typedData: parsed, ...shown.hidden ? { hiddenChars: true } : {} };
|
|
1652
2133
|
}
|
|
1653
2134
|
if (method === "eth_sendTransaction") {
|
|
1654
2135
|
const tx = params[0] ?? {};
|
|
@@ -1697,16 +2178,33 @@ function overpriced(shown, fresh) {
|
|
|
1697
2178
|
if (shown.maxFeePerGas !== void 0 && fresh.maxFeePerGas !== void 0) return big(shown.maxFeePerGas) > STALE_FEE_FACTOR * big(fresh.maxFeePerGas);
|
|
1698
2179
|
return false;
|
|
1699
2180
|
}
|
|
2181
|
+
function gasLimitExcessive(limit, estimate) {
|
|
2182
|
+
const a = estimate * 3n / 2n;
|
|
2183
|
+
const b = estimate + 50000n;
|
|
2184
|
+
return limit > (a > b ? a : b);
|
|
2185
|
+
}
|
|
1700
2186
|
async function prepareTransaction(deps, network, from, tx) {
|
|
1701
2187
|
const call = { from, to: tx.to, value: hex(BigInt(tx.value)), ...tx.data ? { data: tx.data } : {} };
|
|
2188
|
+
const estimate = () => deps.rpc(network, "eth_estimateGas", [call]);
|
|
1702
2189
|
const [nonceHex, block, gasHex] = await Promise.all([
|
|
1703
2190
|
deps.rpc(network, "eth_getTransactionCount", [from, "pending"]),
|
|
1704
2191
|
deps.rpc(network, "eth_getBlockByNumber", ["latest", false]),
|
|
1705
|
-
tx.gas ?
|
|
2192
|
+
tx.gas ? estimate().then((v) => v, () => null) : estimate()
|
|
1706
2193
|
]);
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
2194
|
+
const nonce = safeNonce(big(nonceHex));
|
|
2195
|
+
if (!tx.gas) {
|
|
2196
|
+
const estimated = big(gasHex);
|
|
2197
|
+
return withFees(network.chainId, nonce, (estimated * 120n + 99n) / 100n, await readFees(deps, network, block));
|
|
2198
|
+
}
|
|
2199
|
+
const limit = big(tx.gas);
|
|
2200
|
+
const prepared = withFees(network.chainId, nonce, limit, await readFees(deps, network, block));
|
|
2201
|
+
let est = null;
|
|
2202
|
+
try {
|
|
2203
|
+
est = typeof gasHex === "string" ? big(gasHex) : null;
|
|
2204
|
+
} catch {
|
|
2205
|
+
est = null;
|
|
2206
|
+
}
|
|
2207
|
+
return est !== null && est > 0n ? { ...prepared, gasCheck: { estimate: hex(est), excessive: gasLimitExcessive(limit, est) } } : prepared;
|
|
1710
2208
|
}
|
|
1711
2209
|
function createSignHandler(deps) {
|
|
1712
2210
|
const tails = /* @__PURE__ */ new Map();
|
|
@@ -1722,7 +2220,8 @@ function createSignHandler(deps) {
|
|
|
1722
2220
|
};
|
|
1723
2221
|
return async ({ origin, network, method, params, accounts }) => {
|
|
1724
2222
|
const via = deps.approvals.viaFor(origin);
|
|
1725
|
-
const
|
|
2223
|
+
const allowBlindHash = method === "personal_sign" && deps.allowBlindHash ? await deps.allowBlindHash() : false;
|
|
2224
|
+
const info = classifySignRequest({ network, method, params, accounts, origin, ...via ? { via } : {}, ...allowBlindHash ? { allowBlindHash: true } : {} });
|
|
1726
2225
|
if ("code" in info) throw RouterError.from(info);
|
|
1727
2226
|
const prepared = info.kind === "send-transaction" ? await prepareTransaction(deps, network, info.account, info.tx) : void 0;
|
|
1728
2227
|
const preview = deps.preview ? await deps.preview({ network, request: info, accounts }).catch(() => null) : null;
|
|
@@ -1732,14 +2231,20 @@ function createSignHandler(deps) {
|
|
|
1732
2231
|
const key = `${network.caip2}|${lc(info.account)}`;
|
|
1733
2232
|
return serial(key, async () => {
|
|
1734
2233
|
let shown = prepared;
|
|
2234
|
+
let reasks = 0;
|
|
1735
2235
|
for (; ; ) {
|
|
1736
2236
|
const [nonceHex, fees] = await Promise.all([deps.rpc(network, "eth_getTransactionCount", [info.account, "pending"]), readFees(deps, network)]);
|
|
1737
2237
|
const local = nextNonce.get(key) ?? 0;
|
|
1738
2238
|
const nodeNonce = safeNonce(big(nonceHex));
|
|
1739
2239
|
const nonce = nodeNonce > local ? nodeNonce : local;
|
|
1740
2240
|
if (outgrown(shown, fees)) {
|
|
1741
|
-
|
|
1742
|
-
|
|
2241
|
+
const next = withFees(network.chainId, nonce, big(shown.gasLimit), fees);
|
|
2242
|
+
const ceiling = deps.feeCeiling?.(network);
|
|
2243
|
+
if (ceiling !== void 0 && BigInt(next.maxFee) > ceiling) throw new RouterError(RELAY_ERRORS.internal, `The network fee rose above the wallet's limit for ${network.name}; nothing was signed.`);
|
|
2244
|
+
if (reasks >= MAX_FEE_REASKS) throw new RouterError(RELAY_ERRORS.internal, "The network fee kept rising while you were reviewing; nothing was signed. Try again later.");
|
|
2245
|
+
reasks++;
|
|
2246
|
+
await deps.approvals.request({ origin, chain: network.caip2, kind: info.kind, payload: { ...payload, prepared: next, repriced: true, repricedFrom: shown.maxFee } }, { followUp: true });
|
|
2247
|
+
shown = next;
|
|
1743
2248
|
continue;
|
|
1744
2249
|
}
|
|
1745
2250
|
const fresh = withFees(network.chainId, nonce, big(shown.gasLimit), fees);
|
|
@@ -1769,7 +2274,7 @@ function createSignHandler(deps) {
|
|
|
1769
2274
|
try {
|
|
1770
2275
|
switch (info.kind) {
|
|
1771
2276
|
case "sign-message":
|
|
1772
|
-
return await handle.signer.signMessage(
|
|
2277
|
+
return await handle.signer.signMessage(personalSignInput(info.raw));
|
|
1773
2278
|
case "sign-typed-data":
|
|
1774
2279
|
return await handle.signer.signTypedData(info.typedData);
|
|
1775
2280
|
}
|
|
@@ -1827,7 +2332,7 @@ function chainTxMatcher(expected) {
|
|
|
1827
2332
|
}
|
|
1828
2333
|
|
|
1829
2334
|
// src/swap.ts
|
|
1830
|
-
import { validQuote } from "@anywalletkit/protocol-swap2-core";
|
|
2335
|
+
import { slippageFloor, validQuote } from "@anywalletkit/protocol-swap2-core";
|
|
1831
2336
|
import { typedDataHash } from "@anywalletkit/wallet-evm";
|
|
1832
2337
|
async function quoteRoutes(providers, params, options = {}) {
|
|
1833
2338
|
const timeoutMs = options.timeoutMs ?? 2e4;
|
|
@@ -1861,7 +2366,14 @@ async function quoteRoutes(providers, params, options = {}) {
|
|
|
1861
2366
|
candidates.push(p);
|
|
1862
2367
|
}
|
|
1863
2368
|
const pending = new Set(candidates.map((p) => p.name));
|
|
1864
|
-
const
|
|
2369
|
+
const depositAddressRoute = options.depositAddressRoute;
|
|
2370
|
+
const rankOptions = {
|
|
2371
|
+
...params.slippageBps !== void 0 ? { slippageBps: params.slippageBps } : {},
|
|
2372
|
+
...options.depositAddressProviders ? { depositAddressProviders: options.depositAddressProviders } : {},
|
|
2373
|
+
...depositAddressRoute ? { depositAddressRoute: (q) => depositAddressRoute(q, params) } : {}
|
|
2374
|
+
};
|
|
2375
|
+
const { slippageBps: _slippage, ...shapeParams } = params;
|
|
2376
|
+
const view = () => ({ routes: rank(Object.values(quotes), rankOptions), pending: [...pending], refusals: { ...refusals }, quotes: { ...quotes } });
|
|
1865
2377
|
const emit = () => options.onUpdate?.(view());
|
|
1866
2378
|
emit();
|
|
1867
2379
|
await Promise.all(
|
|
@@ -1876,7 +2388,7 @@ async function quoteRoutes(providers, params, options = {}) {
|
|
|
1876
2388
|
const q = await Promise.race([p.getQuote({ ...params, dry: true, onRefusal: (r) => {
|
|
1877
2389
|
refusal = r;
|
|
1878
2390
|
} }, ctrl.signal), aborted]);
|
|
1879
|
-
if (q && validQuote(q, { ...
|
|
2391
|
+
if (q && validQuote(q, { ...shapeParams, dry: true }, p.name)) quotes[p.name] = q;
|
|
1880
2392
|
else refusals[p.name] = refusal ?? (q ? "returned an invalid quote" : "no route for this pair right now");
|
|
1881
2393
|
} catch (e) {
|
|
1882
2394
|
refusals[p.name] = ctrl.signal.aborted ? String(ctrl.signal.reason instanceof Error ? ctrl.signal.reason.message : ctrl.signal.reason ?? "cancelled") : e instanceof Error ? e.message : String(e);
|
|
@@ -1890,16 +2402,50 @@ async function quoteRoutes(providers, params, options = {}) {
|
|
|
1890
2402
|
);
|
|
1891
2403
|
return view();
|
|
1892
2404
|
}
|
|
1893
|
-
function
|
|
2405
|
+
function gapBps(dest, min) {
|
|
2406
|
+
if (dest <= 0n || min >= dest) return 0;
|
|
2407
|
+
return Number((((dest - min) * 10000n + dest - 1n) / dest).toString());
|
|
2408
|
+
}
|
|
2409
|
+
function rank(quotes, options = {}) {
|
|
1894
2410
|
const sorted = [...quotes].sort((a, b) => BigInt(b.destAmount) > BigInt(a.destAmount) ? 1 : BigInt(b.destAmount) < BigInt(a.destAmount) ? -1 : a.feeUsd - b.feeUsd || a.etaSeconds - b.etaSeconds);
|
|
1895
2411
|
const fastest = sorted.length ? sorted.reduce((f, q) => q.etaSeconds < f.etaSeconds ? q : f) : null;
|
|
1896
|
-
|
|
2412
|
+
const depositProviders = new Set(options.depositAddressProviders ?? []);
|
|
2413
|
+
const paysAssignedAddress = (q) => {
|
|
2414
|
+
if (depositProviders.has(q.provider)) return true;
|
|
2415
|
+
if (!options.depositAddressRoute) return false;
|
|
2416
|
+
try {
|
|
2417
|
+
return options.depositAddressRoute(q) === true;
|
|
2418
|
+
} catch {
|
|
2419
|
+
return true;
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
const assigned = new Map(sorted.map((q) => [q, paysAssignedAddress(q)]));
|
|
2423
|
+
const s = options.slippageBps;
|
|
2424
|
+
const warningsOf = (q) => {
|
|
2425
|
+
const w = [];
|
|
2426
|
+
if (s !== void 0 && Number.isInteger(s) && s >= 0 && s < 1e4) {
|
|
2427
|
+
const dest = BigInt(q.destAmount);
|
|
2428
|
+
const min = BigInt(q.minDestAmount);
|
|
2429
|
+
if (min < slippageFloor(dest, s)) w.push({ kind: "min-below-slippage", slippageBps: s, minGapBps: gapBps(dest, min) });
|
|
2430
|
+
}
|
|
2431
|
+
if (assigned.get(q)) w.push({ kind: "deposit-address" });
|
|
2432
|
+
return w;
|
|
2433
|
+
};
|
|
2434
|
+
const warned = new Map(sorted.map((q) => [q, warningsOf(q)]));
|
|
2435
|
+
const verifiableExists = sorted.some((q) => !assigned.get(q));
|
|
2436
|
+
const best = sorted.find((q) => {
|
|
2437
|
+
const w = warned.get(q);
|
|
2438
|
+
return !w.some((x) => x.kind === "min-below-slippage") && !(w.some((x) => x.kind === "deposit-address") && verifiableExists);
|
|
2439
|
+
});
|
|
2440
|
+
const ordered = best ? [best, ...sorted.filter((q) => q !== best)] : sorted;
|
|
2441
|
+
return ordered.map((q) => {
|
|
1897
2442
|
const poolFee = q.providerData?.poolFee;
|
|
1898
2443
|
const feeNote = typeof poolFee === "string" && poolFee && q.feeUsd === 0 ? poolFee === "pools" ? "pool fees in price" : `pool ${poolFee} in price` : void 0;
|
|
1899
2444
|
const tags = [];
|
|
1900
|
-
if (
|
|
2445
|
+
if (q === best) tags.push("best");
|
|
1901
2446
|
if (fastest && q.provider === fastest.provider) tags.push("fastest");
|
|
1902
|
-
|
|
2447
|
+
const warnings = warned.get(q);
|
|
2448
|
+
return { provider: q.provider, destAmount: q.destAmount, minDestAmount: q.minDestAmount, feeUsd: q.feeUsd, etaSeconds: q.etaSeconds, validUntilMs: q.validUntilMs, tags, ...feeNote ? { feeNote } : {}, ...warnings.length ? { warnings } : {} };
|
|
1903
2449
|
});
|
|
1904
2450
|
}
|
|
1905
2451
|
var FLOOR_TOLERANCE_BPS = 50n;
|
|
@@ -1918,18 +2464,21 @@ function depositOf(s, approvals) {
|
|
|
1918
2464
|
if (!s.data.startsWith(TRANSFER_SELECTOR) || s.data.length < 10 + 64) return void 0;
|
|
1919
2465
|
return `0x${s.data.slice(10 + 24, 10 + 64)}`;
|
|
1920
2466
|
}
|
|
2467
|
+
var pct = (bps) => `${Number((bps / 100).toFixed(2))}%`;
|
|
1921
2468
|
function swapApprovalPayload(plan, meta) {
|
|
1922
2469
|
const isOwn = meta.own.some((a) => lc3(a) === lc3(plan.recipient));
|
|
2470
|
+
const assigned = meta.depositAddressAssignedByProvider === true;
|
|
1923
2471
|
const checks = [{ status: "ok", label: `Built by the ${meta.providerLabel} module` }];
|
|
2472
|
+
const depositCheck = assigned ? { status: "warn", label: `Deposit address assigned by the ${meta.providerLabel} server \u2014 funds go to an address the wallet cannot verify` } : { status: "ok", label: `Deposit address from the ${meta.providerLabel} quote` };
|
|
1924
2473
|
const s = plan.source;
|
|
1925
2474
|
let source;
|
|
1926
2475
|
if (s.kind === "call") {
|
|
1927
2476
|
const deposit = depositOf(s, plan.approvals.length);
|
|
1928
2477
|
source = { kind: "call", to: s.to, ...s.data ? { data: s.data } : {}, value: s.value.toString(), ...deposit ? { deposit } : {} };
|
|
1929
|
-
checks.push(deposit
|
|
2478
|
+
checks.push(deposit || assigned ? depositCheck : { status: "ok", label: `Calls the ${meta.providerLabel} contract` });
|
|
1930
2479
|
} else if (s.kind === "token-transfer") {
|
|
1931
2480
|
source = { kind: "token-transfer", token: s.token, recipient: s.recipient, amount: s.amount.toString(), ...s.memo ? { memo: s.memo } : {} };
|
|
1932
|
-
checks.push(
|
|
2481
|
+
checks.push(depositCheck);
|
|
1933
2482
|
} else if (s.kind === "serialized") {
|
|
1934
2483
|
source = { kind: "serialized" };
|
|
1935
2484
|
checks.push({ status: "ok", label: "Balance changes are proven before signing (SDK guard)" });
|
|
@@ -1940,7 +2489,14 @@ function swapApprovalPayload(plan, meta) {
|
|
|
1940
2489
|
}
|
|
1941
2490
|
for (const a of plan.approvals) checks.push(a.amount > plan.sourceAmount ? { status: "warn", label: "Approval is larger than this swap needs" } : { status: "ok", label: "Approval is exactly the amount needed" });
|
|
1942
2491
|
if (plan.approvals.length > 1) checks.push({ status: "ok", label: `${plan.approvals.length} approvals (reset to 0 first)` });
|
|
1943
|
-
checks.push(isOwn ? { status: "
|
|
2492
|
+
checks.push(!isOwn ? { status: "warn", label: `Funds arrive at ${plan.recipient.slice(0, 6)}\u2026${plan.recipient.slice(-4)}, not your address` } : assigned ? { status: "unknown", label: `Delivery to your address is the ${meta.providerLabel} server\u2019s promise \u2014 this transaction does not enforce it` } : { status: "ok", label: "Funds arrive at your address" });
|
|
2493
|
+
let slippage;
|
|
2494
|
+
const sb = meta.slippageBps;
|
|
2495
|
+
if (sb !== void 0 && Number.isInteger(sb) && sb >= 0 && sb < 1e4) {
|
|
2496
|
+
const below = plan.minDestAmount < slippageFloor(plan.destAmount, sb);
|
|
2497
|
+
slippage = { bps: sb, minGapBps: gapBps(plan.destAmount, plan.minDestAmount), below };
|
|
2498
|
+
checks.push(below ? { status: "warn", label: `Minimum is ${pct(slippage.minGapBps)} under the quote \u2014 more than your ${pct(sb)} slippage` } : { status: "ok", label: `Minimum within your ${pct(sb)} slippage` });
|
|
2499
|
+
}
|
|
1944
2500
|
return {
|
|
1945
2501
|
provider: plan.provider,
|
|
1946
2502
|
providerLabel: meta.providerLabel,
|
|
@@ -1957,7 +2513,9 @@ function swapApprovalPayload(plan, meta) {
|
|
|
1957
2513
|
validUntilMs: plan.validUntilMs,
|
|
1958
2514
|
approvals: plan.approvals.map((a) => ({ token: a.token, spender: a.spender, amount: a.amount.toString() })),
|
|
1959
2515
|
source,
|
|
1960
|
-
checks
|
|
2516
|
+
checks,
|
|
2517
|
+
...slippage ? { slippage } : {},
|
|
2518
|
+
...assigned ? { depositAssignedByProvider: true } : {}
|
|
1961
2519
|
};
|
|
1962
2520
|
}
|
|
1963
2521
|
function parseTypedOrder(json) {
|
|
@@ -2420,6 +2978,10 @@ var RecipientBook = class {
|
|
|
2420
2978
|
await this.storage.set(s);
|
|
2421
2979
|
this._state = s;
|
|
2422
2980
|
}
|
|
2981
|
+
/** Forget the cached book: the next read asks storage again (e.g. a storage that only answers once the wallet is unlocked). */
|
|
2982
|
+
reload() {
|
|
2983
|
+
this._state = null;
|
|
2984
|
+
}
|
|
2423
2985
|
/** `known` → the address is one the user paid before; `lookalike` → it imitates one. */
|
|
2424
2986
|
async check(namespace, address) {
|
|
2425
2987
|
const list = await this.list(namespace);
|
|
@@ -2427,9 +2989,243 @@ var RecipientBook = class {
|
|
|
2427
2989
|
return { known, lookalike: known ? null : lookalikeOf(address, list) };
|
|
2428
2990
|
}
|
|
2429
2991
|
};
|
|
2992
|
+
|
|
2993
|
+
// src/mac-storage.ts
|
|
2994
|
+
import { zeroize as zeroize4 } from "@anywalletkit/core";
|
|
2995
|
+
var STORAGE_MAC_FIELD = "anywalletkit.mac/1";
|
|
2996
|
+
var STORAGE_VERSION_FIELD = "anywalletkit.ver/1";
|
|
2997
|
+
var HKDF_INFO2 = "anywalletkit/storage-mac/1";
|
|
2998
|
+
var KEY_BYTES = 32;
|
|
2999
|
+
async function deriveStorageMacKey(dek, crypto2 = globalThis.crypto) {
|
|
3000
|
+
if (dek.length !== 32) throw new VaultError("Vault key must be 32 bytes.", "invalid-input");
|
|
3001
|
+
const ikm = await withSecretBuffer(dek, (b) => crypto2.subtle.importKey("raw", b, "HKDF", false, ["deriveBits"]));
|
|
3002
|
+
const bits = await crypto2.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: new ArrayBuffer(0), info: toArrayBuffer(utf8.encode(HKDF_INFO2)) }, ikm, KEY_BYTES * 8);
|
|
3003
|
+
return new Uint8Array(bits);
|
|
3004
|
+
}
|
|
3005
|
+
function macJsonStorage(inner, options) {
|
|
3006
|
+
const { label, versions } = options;
|
|
3007
|
+
if (versions && label.includes("\n")) throw new VaultError("A versioned store label has no line break.", "invalid-input");
|
|
3008
|
+
const readWithoutKey = options.readWithoutKey ?? "unverified";
|
|
3009
|
+
const writeWithoutKey = options.writeWithoutKey ?? "unsigned";
|
|
3010
|
+
const withKey = async (given, fn) => {
|
|
3011
|
+
const key = given ? Uint8Array.from(given) : await options.key();
|
|
3012
|
+
try {
|
|
3013
|
+
return await fn(key);
|
|
3014
|
+
} finally {
|
|
3015
|
+
if (key) zeroize4(key);
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
const stored = async (key) => {
|
|
3019
|
+
const raw = await inner.get();
|
|
3020
|
+
if (raw === null || raw === void 0) return { state: "empty", value: null };
|
|
3021
|
+
const envelope = isStored(raw);
|
|
3022
|
+
const value = envelope ? raw.value : raw;
|
|
3023
|
+
if (!envelope || raw[STORAGE_MAC_FIELD] === null) return { state: "unsigned", value };
|
|
3024
|
+
if (!key) return { state: "no-key", value };
|
|
3025
|
+
if (!versions) return { state: await verify(key, label, value, raw[STORAGE_MAC_FIELD]) ? "ok" : "invalid", value };
|
|
3026
|
+
const version = STORAGE_VERSION_FIELD in raw ? raw[STORAGE_VERSION_FIELD] : 0;
|
|
3027
|
+
if (version !== 0 && !isVersion(version)) return { state: "invalid", value };
|
|
3028
|
+
if (!await verify(key, label, value, raw[STORAGE_MAC_FIELD], version || void 0)) return { state: "invalid", value };
|
|
3029
|
+
return { state: "ok", value, version };
|
|
3030
|
+
};
|
|
3031
|
+
const put = async (key, value, version) => {
|
|
3032
|
+
const mac = await sign(key, label, value, version);
|
|
3033
|
+
await inner.set(version === void 0 ? { [STORAGE_MAC_FIELD]: mac, value } : { [STORAGE_MAC_FIELD]: mac, [STORAGE_VERSION_FIELD]: version, value });
|
|
3034
|
+
};
|
|
3035
|
+
const seen = async (key) => {
|
|
3036
|
+
const r = await stored(key);
|
|
3037
|
+
return r.state === "ok" ? r.version ?? 0 : 0;
|
|
3038
|
+
};
|
|
3039
|
+
const read = (given) => withKey(given, async (key) => {
|
|
3040
|
+
if (!versions || !key) return stored(key);
|
|
3041
|
+
return versions.exclusive(async (ledger) => {
|
|
3042
|
+
const r = await stored(key);
|
|
3043
|
+
if (r.state === "empty") return await ledger.check(label, null, key) === "ok" ? r : { state: "stale", value: null };
|
|
3044
|
+
if (r.state !== "ok") return r;
|
|
3045
|
+
return await ledger.check(label, r.version ?? 0, key) === "ok" ? r : { ...r, state: "stale" };
|
|
3046
|
+
});
|
|
3047
|
+
});
|
|
3048
|
+
const write = (value, given) => withKey(given, async (key) => {
|
|
3049
|
+
if (!key) throw new VaultError("The wallet is locked: unlock it to change this.", "locked");
|
|
3050
|
+
if (!versions) return put(key, value);
|
|
3051
|
+
await versions.exclusive((ledger) => ledger.advance(label, key, () => seen(key), (version) => put(key, value, version)));
|
|
3052
|
+
});
|
|
3053
|
+
return {
|
|
3054
|
+
read,
|
|
3055
|
+
write,
|
|
3056
|
+
async get() {
|
|
3057
|
+
const r = await read();
|
|
3058
|
+
if (r.state === "ok") return r.value;
|
|
3059
|
+
if (r.state === "no-key" && readWithoutKey === "unverified") return r.value;
|
|
3060
|
+
return null;
|
|
3061
|
+
},
|
|
3062
|
+
async set(value) {
|
|
3063
|
+
await withKey(void 0, async (key) => {
|
|
3064
|
+
if (key) return write(value, key);
|
|
3065
|
+
if (writeWithoutKey === "refuse") throw new VaultError("The wallet is locked: unlock it to change this.", "locked");
|
|
3066
|
+
await inner.set({ [STORAGE_MAC_FIELD]: null, value });
|
|
3067
|
+
});
|
|
3068
|
+
},
|
|
3069
|
+
async rekey(oldKey, newKey) {
|
|
3070
|
+
if (!versions) {
|
|
3071
|
+
const r = await read(oldKey);
|
|
3072
|
+
if (r.state === "ok") await write(r.value, newKey);
|
|
3073
|
+
return r.state;
|
|
3074
|
+
}
|
|
3075
|
+
return versions.exclusive(async (ledger) => {
|
|
3076
|
+
const r = await stored(oldKey);
|
|
3077
|
+
if (r.state === "empty") return await ledger.check(label, null, newKey) === "ok" ? "empty" : "stale";
|
|
3078
|
+
if (r.state !== "ok") return r.state;
|
|
3079
|
+
const version = r.version ?? 0;
|
|
3080
|
+
if (await ledger.check(label, version, newKey) !== "ok") return "stale";
|
|
3081
|
+
if (version > 0) await put(newKey, r.value, version);
|
|
3082
|
+
else await ledger.advance(label, newKey, async () => 0, (v) => put(newKey, r.value, v));
|
|
3083
|
+
return "ok";
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
function isStored(raw) {
|
|
3089
|
+
if (typeof raw !== "object" || raw === null || !(STORAGE_MAC_FIELD in raw) || !("value" in raw)) return false;
|
|
3090
|
+
const mac = raw[STORAGE_MAC_FIELD];
|
|
3091
|
+
return mac === null || typeof mac === "string";
|
|
3092
|
+
}
|
|
3093
|
+
async function hmacKey(key, usage) {
|
|
3094
|
+
if (key.length !== KEY_BYTES) throw new VaultError("A storage MAC key is 32 bytes.", "invalid-input");
|
|
3095
|
+
return withSecretBuffer(key, (b) => globalThis.crypto.subtle.importKey("raw", b, { name: "HMAC", hash: "SHA-256" }, false, [usage]));
|
|
3096
|
+
}
|
|
3097
|
+
function macInput(label, value, version) {
|
|
3098
|
+
return toArrayBuffer(utf8.encode(version === void 0 ? `${label}
|
|
3099
|
+
${canonicalJson(value)}` : `${label}
|
|
3100
|
+
${version}
|
|
3101
|
+
${canonicalJson(value)}`));
|
|
3102
|
+
}
|
|
3103
|
+
async function sign(key, label, value, version) {
|
|
3104
|
+
return toBase64(new Uint8Array(await globalThis.crypto.subtle.sign("HMAC", await hmacKey(key, "sign"), macInput(label, value, version))));
|
|
3105
|
+
}
|
|
3106
|
+
async function verify(key, label, value, mac, version) {
|
|
3107
|
+
let bytes;
|
|
3108
|
+
try {
|
|
3109
|
+
bytes = fromBase64(mac);
|
|
3110
|
+
} catch {
|
|
3111
|
+
return false;
|
|
3112
|
+
}
|
|
3113
|
+
return globalThis.crypto.subtle.verify("HMAC", await hmacKey(key, "verify"), toArrayBuffer(bytes), macInput(label, value, version));
|
|
3114
|
+
}
|
|
3115
|
+
var isVersion = (x) => typeof x === "number" && Number.isSafeInteger(x) && x > 0;
|
|
3116
|
+
function canonicalJson(value) {
|
|
3117
|
+
return JSON.stringify(sortKeys(JSON.parse(JSON.stringify(value ?? null))));
|
|
3118
|
+
}
|
|
3119
|
+
function sortKeys(x) {
|
|
3120
|
+
if (Array.isArray(x)) return x.map(sortKeys);
|
|
3121
|
+
if (x === null || typeof x !== "object") return x;
|
|
3122
|
+
const out = {};
|
|
3123
|
+
for (const k of Object.keys(x).sort()) out[k] = sortKeys(x[k]);
|
|
3124
|
+
return out;
|
|
3125
|
+
}
|
|
3126
|
+
var LEDGER_LABEL = "anywalletkit/storage-versions/1";
|
|
3127
|
+
function storageVersionLedger(options) {
|
|
3128
|
+
const label = options.label ?? LEDGER_LABEL;
|
|
3129
|
+
const acceptUnversioned = options.acceptUnversioned ?? false;
|
|
3130
|
+
const now = options.now ?? Date.now;
|
|
3131
|
+
let tail = Promise.resolve();
|
|
3132
|
+
const queue = (fn) => {
|
|
3133
|
+
const run = tail.then(fn, fn);
|
|
3134
|
+
tail = run.then(() => void 0, () => void 0);
|
|
3135
|
+
return run;
|
|
3136
|
+
};
|
|
3137
|
+
const high = async () => {
|
|
3138
|
+
const v = await options.mark.get();
|
|
3139
|
+
return isVersion(v) ? v : 0;
|
|
3140
|
+
};
|
|
3141
|
+
const parse = async (raw, key) => {
|
|
3142
|
+
if (!isStored(raw) || raw[STORAGE_MAC_FIELD] === null) return null;
|
|
3143
|
+
const epoch = raw[STORAGE_VERSION_FIELD];
|
|
3144
|
+
const v = raw.value;
|
|
3145
|
+
if (!isVersion(epoch) || typeof v !== "object" || v === null || !isVersion(v.base) || typeof v.sealed !== "boolean") return null;
|
|
3146
|
+
if (typeof v.versions !== "object" || v.versions === null || !Object.values(v.versions).every(isVersion)) return null;
|
|
3147
|
+
if (!await verify(key, label, v, raw[STORAGE_MAC_FIELD], epoch)) return null;
|
|
3148
|
+
return { epoch, base: v.base, sealed: v.sealed, versions: { ...v.versions } };
|
|
3149
|
+
};
|
|
3150
|
+
const load = async (key) => {
|
|
3151
|
+
const mark = await high();
|
|
3152
|
+
const raw = await options.manifest.get();
|
|
3153
|
+
if (raw === null || raw === void 0) return { state: mark > 0 ? "stale" : "empty" };
|
|
3154
|
+
const manifest = await parse(raw, key);
|
|
3155
|
+
if (!manifest) return { state: "invalid" };
|
|
3156
|
+
if (manifest.epoch < mark) return { state: "stale", manifest };
|
|
3157
|
+
if (manifest.epoch > mark) await options.mark.set(manifest.epoch);
|
|
3158
|
+
return { state: "ok", manifest };
|
|
3159
|
+
};
|
|
3160
|
+
const store = async (m, key) => {
|
|
3161
|
+
const value = { base: m.base, sealed: m.sealed, versions: m.versions };
|
|
3162
|
+
await options.manifest.set({ [STORAGE_MAC_FIELD]: await sign(key, label, value, m.epoch), [STORAGE_VERSION_FIELD]: m.epoch, value });
|
|
3163
|
+
if (m.epoch > await high()) await options.mark.set(m.epoch);
|
|
3164
|
+
};
|
|
3165
|
+
const fresh = async (sealed, prior, key) => {
|
|
3166
|
+
const floor = Math.max(await high(), Math.floor(now()), prior ? Math.max(prior.epoch, prior.base, ...Object.values(prior.versions)) : 0);
|
|
3167
|
+
if (!Number.isSafeInteger(floor + 2)) throw new VaultError("Storage version out of range.", "invalid-input");
|
|
3168
|
+
const m = { epoch: floor + 1, base: floor + 1, sealed, versions: {} };
|
|
3169
|
+
await store(m, key);
|
|
3170
|
+
return m;
|
|
3171
|
+
};
|
|
3172
|
+
const record = (m, name, version, key) => store({ ...m, epoch: m.epoch + 1, versions: { ...m.versions, [name]: version } }, key);
|
|
3173
|
+
const named = (name) => {
|
|
3174
|
+
if (name === label || name.includes("\n")) throw new VaultError("Not a store label.", "invalid-input");
|
|
3175
|
+
};
|
|
3176
|
+
const ops = {
|
|
3177
|
+
async check(name, version, key) {
|
|
3178
|
+
named(name);
|
|
3179
|
+
const l = await load(key);
|
|
3180
|
+
if (l.state === "empty") return version === null || version === 0 && acceptUnversioned ? "ok" : "stale";
|
|
3181
|
+
if (l.state !== "ok") return "stale";
|
|
3182
|
+
const m = l.manifest;
|
|
3183
|
+
const listed = m.versions[name];
|
|
3184
|
+
if (listed === void 0 && version === null) return m.sealed ? "stale" : "ok";
|
|
3185
|
+
if (listed === void 0 && version === 0) return !m.sealed && acceptUnversioned ? "ok" : "stale";
|
|
3186
|
+
if (version === null || version === 0) return "stale";
|
|
3187
|
+
if (version === listed) return "ok";
|
|
3188
|
+
if (version !== (listed ?? m.base) + 1) return "stale";
|
|
3189
|
+
await record(m, name, version, key).catch(() => void 0);
|
|
3190
|
+
return "ok";
|
|
3191
|
+
},
|
|
3192
|
+
async advance(name, key, seen, write) {
|
|
3193
|
+
named(name);
|
|
3194
|
+
const l = await load(key);
|
|
3195
|
+
const before = await seen();
|
|
3196
|
+
const m = l.state === "ok" ? l.manifest : await fresh(l.state !== "empty" || before > 0, l.manifest, key);
|
|
3197
|
+
const version = Math.max(m.versions[name] ?? m.base, before) + 1;
|
|
3198
|
+
if (!Number.isSafeInteger(version)) throw new VaultError("Storage version out of range.", "invalid-input");
|
|
3199
|
+
await write(version);
|
|
3200
|
+
await record(m, name, version, key);
|
|
3201
|
+
return version;
|
|
3202
|
+
}
|
|
3203
|
+
};
|
|
3204
|
+
return {
|
|
3205
|
+
exclusive: (fn) => queue(() => fn(ops)),
|
|
3206
|
+
condemn: (key) => queue(async () => {
|
|
3207
|
+
await fresh(true, (await load(key)).manifest, key);
|
|
3208
|
+
}),
|
|
3209
|
+
rekey: (oldKey, newKey) => queue(async () => {
|
|
3210
|
+
const l = await load(oldKey);
|
|
3211
|
+
if (l.state === "ok") await store({ ...l.manifest, epoch: l.manifest.epoch + 1 }, newKey);
|
|
3212
|
+
return l.state;
|
|
3213
|
+
}),
|
|
3214
|
+
read: (key) => queue(async () => {
|
|
3215
|
+
const l = await load(key);
|
|
3216
|
+
return { state: l.state, ...l.manifest ?? { epoch: 0, base: 0, sealed: false, versions: {} } };
|
|
3217
|
+
}),
|
|
3218
|
+
reset: () => queue(async () => {
|
|
3219
|
+
await options.manifest.set(null);
|
|
3220
|
+
await options.mark.set(null);
|
|
3221
|
+
})
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
2430
3224
|
export {
|
|
2431
3225
|
ACTIVE_STATUSES,
|
|
2432
3226
|
APPROVALS_MAX_PENDING_PER_ORIGIN,
|
|
3227
|
+
APPROVALS_MAX_PENDING_PER_SITE,
|
|
3228
|
+
APPROVALS_MAX_PENDING_TOTAL,
|
|
2433
3229
|
ApprovalQueue,
|
|
2434
3230
|
BTC_CHAIN,
|
|
2435
3231
|
BTC_ESPLORA,
|
|
@@ -2437,9 +3233,12 @@ export {
|
|
|
2437
3233
|
DEFAULT_CHAIN,
|
|
2438
3234
|
DEFAULT_NETWORKS,
|
|
2439
3235
|
FLOOR_TOLERANCE_BPS,
|
|
3236
|
+
GETLOGS_MAX_SPAN_UNCONNECTED,
|
|
2440
3237
|
KEYRING_STORAGE_KEY,
|
|
2441
3238
|
LOOKALIKE_PREFIX,
|
|
2442
3239
|
LOOKALIKE_SUFFIX,
|
|
3240
|
+
MAX_FEE_REASKS,
|
|
3241
|
+
MULTI_LABEL_SUFFIXES,
|
|
2443
3242
|
NEAR_CHAIN,
|
|
2444
3243
|
NEAR_PROVIDERS,
|
|
2445
3244
|
NON_EVM_CHAINS,
|
|
@@ -2460,8 +3259,11 @@ export {
|
|
|
2460
3259
|
RouterError,
|
|
2461
3260
|
SESSION_DEFAULT_AUTOLOCK_MS,
|
|
2462
3261
|
SIGNING_METHODS,
|
|
3262
|
+
SIWE_FORBIDDEN_CHARS,
|
|
2463
3263
|
SOLANA_CHAIN,
|
|
2464
3264
|
SOLANA_RPCS,
|
|
3265
|
+
STORAGE_MAC_FIELD,
|
|
3266
|
+
STORAGE_VERSION_FIELD,
|
|
2465
3267
|
Session,
|
|
2466
3268
|
SwapTracker,
|
|
2467
3269
|
SwapsStore,
|
|
@@ -2473,6 +3275,7 @@ export {
|
|
|
2473
3275
|
TON_ENDPOINTS,
|
|
2474
3276
|
TRON_CHAIN,
|
|
2475
3277
|
TRON_PROVIDERS,
|
|
3278
|
+
TokenBucket,
|
|
2476
3279
|
TonConnectError2 as TonConnectError,
|
|
2477
3280
|
VAULT_DEFAULT_ITERATIONS,
|
|
2478
3281
|
VAULT_LIMITS,
|
|
@@ -2487,6 +3290,7 @@ export {
|
|
|
2487
3290
|
ZCASH_CHAIN,
|
|
2488
3291
|
approvalChallenge,
|
|
2489
3292
|
caip2FromChainId,
|
|
3293
|
+
canonicalJson,
|
|
2490
3294
|
chainHex,
|
|
2491
3295
|
chainTxMatcher,
|
|
2492
3296
|
checkSenderOrigin,
|
|
@@ -2499,6 +3303,7 @@ export {
|
|
|
2499
3303
|
createSignHandler,
|
|
2500
3304
|
decodedMessage,
|
|
2501
3305
|
derToRaw,
|
|
3306
|
+
deriveStorageMacKey,
|
|
2502
3307
|
describeSiwe,
|
|
2503
3308
|
earnApprovalPayload,
|
|
2504
3309
|
earnPlanMatchers,
|
|
@@ -2506,6 +3311,7 @@ export {
|
|
|
2506
3311
|
evmCallMatcher,
|
|
2507
3312
|
executionFloor,
|
|
2508
3313
|
fromBase64Url,
|
|
3314
|
+
gasLimitExcessive,
|
|
2509
3315
|
hostOf,
|
|
2510
3316
|
isCatalogChain,
|
|
2511
3317
|
isEnvelopeV1,
|
|
@@ -2519,6 +3325,7 @@ export {
|
|
|
2519
3325
|
isRelayToContent,
|
|
2520
3326
|
levenshtein,
|
|
2521
3327
|
lookalikeOf,
|
|
3328
|
+
macJsonStorage,
|
|
2522
3329
|
memoryAlarms,
|
|
2523
3330
|
memoryApprovalWindow,
|
|
2524
3331
|
memoryJsonStorage,
|
|
@@ -2543,11 +3350,15 @@ export {
|
|
|
2543
3350
|
rank,
|
|
2544
3351
|
rawToDer,
|
|
2545
3352
|
relayError,
|
|
3353
|
+
revealHidden,
|
|
2546
3354
|
reviewEarnPlan,
|
|
2547
3355
|
reviewSwapPlan,
|
|
2548
3356
|
sameTonConnectAddress,
|
|
3357
|
+
siteOf,
|
|
3358
|
+
siweCrossSite,
|
|
2549
3359
|
siweProblems,
|
|
2550
3360
|
stampOrigin,
|
|
3361
|
+
storageVersionLedger,
|
|
2551
3362
|
swapApprovalPayload,
|
|
2552
3363
|
toBase64Url,
|
|
2553
3364
|
toRelayError,
|