@fastnear/wallet-adapter
Version:
Wallet adapter implementations for Meteor Wallet and Near Mobile
437 lines • 17.9 kB
JavaScript
/* ⋈ 🏃🏻💨 FastNear Wallet Adapters - CJS (@fastnear/wallet-adapter version 1.2.0) */
/* https://www.npmjs.com/package/@fastnear/wallet-adapter/v/1.2.0 */
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var meteor_exports = {};
__export(meteor_exports, {
createMeteorAdapter: () => createMeteorAdapter
});
module.exports = __toCommonJS(meteor_exports);
var import_borsh = require("@fastnear/borsh");
var import_utils = require("@fastnear/utils");
var import_actions = require("./actions.js");
var import_rpc = require("./rpc.js");
var import_errors = require("./errors.js");
var import_storage = require("./storage.js");
const METEOR_DEFAULT_WALLET_BASE = "https://wallet.meteorwallet.app";
const METEOR_CONNECTION_PING_MS = 450;
const METEOR_POPUP_WIDTH = 390;
const METEOR_POPUP_HEIGHT = 650;
const LEGACY_AUTH_KEY_SUFFIX = "_meteor_wallet_auth_key";
const randomUid = /* @__PURE__ */ __name(() => {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
return `meteor-${Date.now()}-${Math.floor(Math.random() * 1e5)}`;
}, "randomUid");
const popupFeatures = /* @__PURE__ */ __name(() => {
if (typeof window === "undefined" || window.top == null) {
return `popup=1,width=${METEOR_POPUP_WIDTH},height=${METEOR_POPUP_HEIGHT}`;
}
const y = window.top.outerHeight / 2 + window.top.screenY - METEOR_POPUP_HEIGHT / 2;
const x = window.top.outerWidth / 2 + window.top.screenX - METEOR_POPUP_WIDTH / 2;
return `popup=1,width=${METEOR_POPUP_WIDTH},height=${METEOR_POPUP_HEIGHT},top=${y},left=${x}`;
}, "popupFeatures");
const isUserRejectedTag = /* @__PURE__ */ __name((tag) => {
return tag === "USER_CANCELLED" || tag === "WINDOW_CLOSED" || tag === "INCOMPLETE_ACTION";
}, "isUserRejectedTag");
const mapMeteorError = /* @__PURE__ */ __name((message, endTags) => {
const tags = endTags ?? [];
const lastTag = tags[tags.length - 1];
if (isUserRejectedTag(lastTag)) {
return new import_errors.UserRejectedError(lastTag ?? "USER_REJECTED", message, { details: { endTags: tags } });
}
if (lastTag === "POPUP_WINDOW_OPEN_FAILED" || lastTag === "POPUP_WINDOW_REFUSED") {
return new import_errors.TransportError(lastTag, message, { details: { endTags: tags } });
}
return new import_errors.TransportError(lastTag ?? "METEOR_ACTION_FAILED", message, {
details: { endTags: tags }
});
}, "mapMeteorError");
const ensureNetwork = /* @__PURE__ */ __name((network) => {
if (network !== "mainnet" && network !== "testnet") {
throw new import_errors.TransportError("INVALID_NETWORK", `Unsupported network: ${network}`);
}
return network;
}, "ensureNetwork");
const toMeteorTxPayload = /* @__PURE__ */ __name((tx) => {
const encoded = (0, import_borsh.serialize)(import_utils.SCHEMA.Transaction, (0, import_utils.mapTransaction)(tx));
return (0, import_utils.bytesToBase64)(new Uint8Array(encoded));
}, "toMeteorTxPayload");
const normalizeActionError = /* @__PURE__ */ __name((error) => {
if (error instanceof import_errors.TransportError || error instanceof import_errors.UserRejectedError) return error;
if (error instanceof Error) return new import_errors.TransportError("METEOR_ACTION_FAILED", error.message, { cause: error });
return new import_errors.TransportError("METEOR_ACTION_FAILED", "Meteor action failed", { details: error });
}, "normalizeActionError");
const createMeteorAdapter = /* @__PURE__ */ __name((options = {}) => {
const storage = options.storage ?? (0, import_storage.createDefaultStorage)();
const walletBaseUrl = options.walletBaseUrl ?? METEOR_DEFAULT_WALLET_BASE;
const appKeyPrefix = options.appKeyPrefix ?? "near_app";
const openWindow = options.openWindow ?? ((url, name, features) => {
if (typeof window === "undefined") return null;
return window.open(url, name ?? "MeteorWallet", features ?? popupFeatures());
});
const rpcForNetwork = (0, import_rpc.createRpcFactory)(options.getNetworkProviders);
const walletOrigin = new URL(walletBaseUrl).origin;
const authStorageKey = /* @__PURE__ */ __name((network) => `${appKeyPrefix}${LEGACY_AUTH_KEY_SUFFIX}:${network}`, "authStorageKey");
const legacyAuthKey = `${appKeyPrefix}${LEGACY_AUTH_KEY_SUFFIX}`;
let extensionListenerAttached = false;
let activeConnection = null;
const loadAuth = /* @__PURE__ */ __name(async (network) => {
const keyed = await (0, import_storage.readJson)(storage, authStorageKey(network), { allKeys: [] });
if (keyed.accountId || (keyed.allKeys?.length ?? 0) > 0) return keyed;
return (0, import_storage.readJson)(storage, legacyAuthKey, { allKeys: [] });
}, "loadAuth");
const saveAuth = /* @__PURE__ */ __name(async (network, state) => {
await (0, import_storage.writeJson)(storage, authStorageKey(network), state);
await (0, import_storage.writeJson)(storage, legacyAuthKey, state);
}, "saveAuth");
const clearAuth = /* @__PURE__ */ __name(async (network) => {
await storage.remove(authStorageKey(network));
}, "clearAuth");
const cleanupConnection = /* @__PURE__ */ __name(() => {
if (activeConnection == null) return;
if (activeConnection.interval != null) clearInterval(activeConnection.interval);
activeConnection.cleanupFns.forEach((fn) => fn());
activeConnection.cleanupFns = [];
activeConnection.popup?.close?.();
activeConnection = null;
}, "cleanupConnection");
const sendConnectionMessage = /* @__PURE__ */ __name((connection) => {
const payload = {
uid: connection.uid,
actionType: connection.actionType,
status: connection.status,
network: connection.network,
endTags: []
};
if (connection.status === "initializing") payload.inputs = connection.inputs;
if (connection.extension != null) {
connection.extension.sendMessageData(payload);
return;
}
if (connection.popup?.postMessage == null) return;
try {
connection.popup.postMessage(payload, connection.walletOrigin);
} catch {
connection.popup.postMessage(payload);
}
}, "sendConnectionMessage");
const closeWithError = /* @__PURE__ */ __name((error) => {
if (activeConnection == null) return;
const reject = activeConnection.reject;
cleanupConnection();
reject(error);
}, "closeWithError");
const closeWithSuccess = /* @__PURE__ */ __name((payload) => {
if (activeConnection == null) return;
const resolve = activeConnection.resolve;
cleanupConnection();
resolve(payload);
}, "closeWithSuccess");
const handleMeteorResponse = /* @__PURE__ */ __name((raw) => {
const data = raw;
if (activeConnection == null) return;
if (data.uid !== activeConnection.uid) return;
if (data.status == null) return;
if (data.status === "attempting_reconnect") {
activeConnection.status = "initializing";
sendConnectionMessage(activeConnection);
return;
}
if (data.status === "connected" && activeConnection.status === "initializing") {
activeConnection.status = "connected";
return;
}
if (data.status === "closed_success") {
closeWithSuccess(data.payload);
return;
}
if (data.status === "closed_fail") {
closeWithError(mapMeteorError(data.message ?? "Meteor action failed", data.endTags));
return;
}
if (data.status === "closed_window") {
closeWithError(
new import_errors.UserRejectedError(
"WINDOW_CLOSED",
data.message ?? "User closed the wallet window",
{ details: { endTags: data.endTags ?? ["WINDOW_CLOSED"] } }
)
);
return;
}
if (data.status === "disconnected") {
closeWithError(new import_errors.TransportError("DISCONNECTED", "Meteor wallet transport disconnected"));
}
}, "handleMeteorResponse");
const attachExtensionListenerIfNeeded = /* @__PURE__ */ __name((extension) => {
if (extension == null || extensionListenerAttached) return;
extension.addMessageDataListener((message) => handleMeteorResponse(message));
extensionListenerAttached = true;
}, "attachExtensionListenerIfNeeded");
const connectAndWaitForResponse = /* @__PURE__ */ __name(async (network, actionType, inputs) => {
if (activeConnection != null) {
activeConnection.reject(
new import_errors.TransportError("NEW_ACTION_STARTED", "A new action was started before the previous action completed")
);
cleanupConnection();
}
const uid = randomUid();
const extension = options.getExtensionBridge?.();
attachExtensionListenerIfNeeded(extension);
let popup;
const cleanupFns = [];
if (extension == null) {
const url = new URL(`${walletBaseUrl}/connect/${network}/${actionType}`);
url.searchParams.set("source", "wpm");
url.searchParams.set("connectionUid", uid);
popup = openWindow(url.toString(), "MeteorWallet", popupFeatures()) ?? void 0;
if (popup == null) {
throw new import_errors.TransportError("POPUP_WINDOW_OPEN_FAILED", "Couldn't open popup window to complete wallet action");
}
if (popup.windowIdPromise != null) {
const popupId = await popup.windowIdPromise;
if (popupId == null) {
throw new import_errors.TransportError("POPUP_WINDOW_OPEN_FAILED", "Couldn't open popup window to complete wallet action");
}
}
if (typeof window !== "undefined") {
const listener = /* @__PURE__ */ __name((event) => handleMeteorResponse(event.data), "listener");
window.addEventListener("message", listener);
cleanupFns.push(() => window.removeEventListener("message", listener));
}
}
return new Promise((resolve, reject) => {
const connection = {
uid,
network,
actionType,
status: "initializing",
inputs,
popup,
extension,
walletOrigin,
cleanupFns,
resolve,
reject
};
activeConnection = connection;
sendConnectionMessage(connection);
connection.interval = setInterval(() => {
if (activeConnection == null) return;
if (activeConnection.popup != null && activeConnection.popup.closed) {
closeWithError(
new import_errors.UserRejectedError(
"WINDOW_CLOSED",
"User closed the wallet window before completing the action",
{ details: { endTags: ["INCOMPLETE_ACTION", "WINDOW_CLOSED"] } }
)
);
return;
}
sendConnectionMessage(activeConnection);
}, METEOR_CONNECTION_PING_MS);
});
}, "connectAndWaitForResponse");
const findSignerPublicKey = /* @__PURE__ */ __name(async (network, accountId, preferredKeys) => {
const rpc = rpcForNetwork(network);
for (const key of preferredKeys) {
try {
await rpc.query({
request_type: "view_access_key",
finality: "optimistic",
account_id: accountId,
public_key: key
});
return key;
} catch {
}
}
const accessKeyList = await rpc.query({
request_type: "view_access_key_list",
finality: "optimistic",
account_id: accountId
});
if (!accessKeyList.keys?.length) {
throw new import_errors.TransportError("NO_ACCESS_KEYS", `No access keys found for account ${accountId}`);
}
return accessKeyList.keys[0].public_key;
}, "findSignerPublicKey");
const prepareMeteorTransactions = /* @__PURE__ */ __name(async (network, signerId, preferredKeys, transactions) => {
const rpc = rpcForNetwork(network);
const block = await rpc.block({ finality: "final" });
const publicKey = await findSignerPublicKey(network, signerId, preferredKeys);
const accessKey = await rpc.query({
request_type: "view_access_key",
finality: "optimistic",
account_id: signerId,
public_key: publicKey
});
return transactions.map((tx, index) => ({
signerId,
publicKey,
nonce: BigInt(accessKey.nonce) + BigInt(index + 1),
receiverId: tx.receiverId,
blockHash: block.header.hash,
actions: (0, import_actions.connectorActionsToFastnearActions)(tx.actions)
}));
}, "prepareMeteorTransactions");
const getAccountsForNetwork = /* @__PURE__ */ __name(async (network) => {
const auth = await loadAuth(network);
if (!auth.accountId) return [];
const publicKey = auth.signedInContract?.public_key ?? auth.allKeys?.[0] ?? "";
return [{ accountId: auth.accountId, publicKey }];
}, "getAccountsForNetwork");
const signIn = /* @__PURE__ */ __name(async ({ network, contractId, methodNames }) => {
const net = ensureNetwork(network);
const generatedKey = (0, import_utils.privateKeyFromRandom)();
const generatedPublicKey = (0, import_utils.publicKeyFromPrivate)(generatedKey);
const inputs = {
type: methodNames && methodNames.length > 0 ? "SELECTED_METHODS" : "ALL_METHODS",
contract_id: contractId,
methods: methodNames ?? [],
public_key: generatedPublicKey
};
const response = await connectAndWaitForResponse(
net,
"login",
inputs
).catch((error) => {
throw normalizeActionError(error);
});
const accountId = response.accountId ?? response.account_id;
if (accountId == null) {
throw new import_errors.TransportError("INVALID_LOGIN_RESPONSE", "Meteor login response did not contain an account id", { details: response });
}
await saveAuth(net, {
accountId,
allKeys: response.allKeys ?? [],
signedInContract: contractId ? { contract_id: contractId, public_key: generatedPublicKey } : void 0
});
return getAccountsForNetwork(net);
}, "signIn");
const signOut = /* @__PURE__ */ __name(async ({ network }) => {
const net = ensureNetwork(network);
const auth = await loadAuth(net);
if (!auth.accountId) return;
if (auth.signedInContract != null) {
await connectAndWaitForResponse(net, "logout", {
accountId: auth.accountId,
contractInfo: auth.signedInContract
}).catch((error) => {
throw normalizeActionError(error);
});
}
await clearAuth(net);
await saveAuth(net, { allKeys: [] });
}, "signOut");
const verifyOwner = /* @__PURE__ */ __name(async ({ network, message, accountId }) => {
const net = ensureNetwork(network);
const auth = await loadAuth(net);
const useAccountId = accountId ?? auth.accountId;
return connectAndWaitForResponse(net, "verify_owner", {
accountId: useAccountId,
message
}).catch((error) => {
throw normalizeActionError(error);
});
}, "verifyOwner");
const signMessage = /* @__PURE__ */ __name(async ({
network,
message,
nonce,
recipient,
callbackUrl,
state,
accountId
}) => {
const net = ensureNetwork(network);
const auth = await loadAuth(net);
const useAccountId = accountId ?? auth.accountId;
const response = await connectAndWaitForResponse(
net,
"sign_message",
{
message,
nonce,
recipient,
callbackUrl: callbackUrl ?? options.getLocation?.(),
state,
accountId: useAccountId
}
).catch((error) => {
throw normalizeActionError(error);
});
return {
...response,
state
};
}, "signMessage");
const signAndSendTransactions = /* @__PURE__ */ __name(async ({
network,
signerId,
transactions
}) => {
const net = ensureNetwork(network);
const auth = await loadAuth(net);
const useSigner = signerId ?? auth.accountId;
if (useSigner == null) throw new import_errors.TransportError("NOT_SIGNED_IN", "Wallet is not signed in");
const prepared = await prepareMeteorTransactions(net, useSigner, auth.allKeys ?? [], transactions);
const serialized = prepared.map(toMeteorTxPayload).join(",");
const response = await connectAndWaitForResponse(net, "sign", {
transactions: serialized
}).catch((error) => {
throw normalizeActionError(error);
});
if (Array.isArray(response?.executionOutcomes)) {
return response.executionOutcomes;
}
if (Array.isArray(response)) return response;
return [response];
}, "signAndSendTransactions");
const signAndSendTransaction = /* @__PURE__ */ __name(async ({
network,
signerId,
receiverId,
actions
}) => {
const result = await signAndSendTransactions({
network,
signerId,
transactions: [{ receiverId, actions }]
});
return result[0];
}, "signAndSendTransaction");
return {
signIn,
signOut,
getAccounts: /* @__PURE__ */ __name(({ network }) => getAccountsForNetwork(ensureNetwork(network)), "getAccounts"),
verifyOwner,
signMessage,
signAndSendTransaction,
signAndSendTransactions
};
}, "createMeteorAdapter");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createMeteorAdapter
});
//# sourceMappingURL=meteor.cjs.map