FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

View File

@@ -0,0 +1,13 @@
Copyright 2022 Solana Mobile Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,3 @@
# `@solana-mobile/mobile-wallet-adapter-protocol-web3js`
This is a convenience wrapper that makes it easy to use common primitives from [@solana/web3.js](https://github.com/solana-labs/solana-web3.js) such as `Transaction` and `Uint8Array` with [@solana-mobile/mobile-wallet-adapter-protocol](https://github.com/solana-mobile/mobile-wallet-adapter/tree/main/js/packages/mobile-wallet-adapter-protocol).

View File

@@ -0,0 +1,124 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _solana_web3_js = require("@solana/web3.js");
let _solana_mobile_mobile_wallet_adapter_protocol = require("@solana-mobile/mobile-wallet-adapter-protocol");
let bs58 = require("bs58");
bs58 = __toESM(bs58);
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/transact.ts
function getPayloadFromTransaction(transaction) {
return fromUint8Array("version" in transaction ? transaction.serialize() : transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
}));
}
function getTransactionFromWireMessage(byteArray) {
const messageOffset = byteArray[0] * _solana_web3_js.SIGNATURE_LENGTH_IN_BYTES + 1;
if (_solana_web3_js.VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return _solana_web3_js.Transaction.from(byteArray);
else return _solana_web3_js.VersionedTransaction.deserialize(byteArray);
}
async function transact(callback, config) {
const augmentedCallback = (wallet) => {
return callback(augmentWalletAPI(wallet));
};
return await (0, _solana_mobile_mobile_wallet_adapter_protocol.transact)(augmentedCallback, config);
}
async function startRemoteScenario(config) {
const { wallet, close, associationUrl } = await (0, _solana_mobile_mobile_wallet_adapter_protocol.startRemoteScenario)(config);
return {
wallet: wallet.then((wallet) => {
return augmentWalletAPI(wallet);
}),
close,
associationUrl
};
}
function augmentWalletAPI(wallet) {
return new Proxy({}, {
get(target, p) {
if (target[p] == null) switch (p) {
case "signAndSendTransactions":
target[p] = async function({ minContextSlot, commitment, skipPreflight, maxRetries, waitForCommitmentToSendNextTransaction, transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const options = {
min_context_slot: minContextSlot,
commitment,
skip_preflight: skipPreflight,
max_retries: maxRetries,
wait_for_commitment_to_send_next_transaction: waitForCommitmentToSendNextTransaction
};
const { signatures: base64EncodedSignatures } = await wallet.signAndSendTransactions({
...rest,
...Object.values(options).some((element) => element != null) ? { options } : null,
payloads
});
return base64EncodedSignatures.map(toUint8Array).map(bs58.default.encode);
};
break;
case "signMessages":
target[p] = async function({ payloads, ...rest }) {
const base64EncodedPayloads = payloads.map(fromUint8Array);
const { signed_payloads: base64EncodedSignedMessages } = await wallet.signMessages({
...rest,
payloads: base64EncodedPayloads
});
return base64EncodedSignedMessages.map(toUint8Array);
};
break;
case "signTransactions":
target[p] = async function({ transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const { signed_payloads: base64EncodedCompiledTransactions } = await wallet.signTransactions({
...rest,
payloads
});
return base64EncodedCompiledTransactions.map(toUint8Array).map(getTransactionFromWireMessage);
};
break;
default:
target[p] = wallet[p];
break;
}
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
//#endregion
exports.startRemoteScenario = startRemoteScenario;
exports.transact = transact;
//# sourceMappingURL=index.browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,124 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _solana_web3_js = require("@solana/web3.js");
let _solana_mobile_mobile_wallet_adapter_protocol = require("@solana-mobile/mobile-wallet-adapter-protocol");
let bs58 = require("bs58");
bs58 = __toESM(bs58);
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/transact.ts
function getPayloadFromTransaction(transaction) {
return fromUint8Array("version" in transaction ? transaction.serialize() : transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
}));
}
function getTransactionFromWireMessage(byteArray) {
const messageOffset = byteArray[0] * _solana_web3_js.SIGNATURE_LENGTH_IN_BYTES + 1;
if (_solana_web3_js.VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return _solana_web3_js.Transaction.from(byteArray);
else return _solana_web3_js.VersionedTransaction.deserialize(byteArray);
}
async function transact(callback, config) {
const augmentedCallback = (wallet) => {
return callback(augmentWalletAPI(wallet));
};
return await (0, _solana_mobile_mobile_wallet_adapter_protocol.transact)(augmentedCallback, config);
}
async function startRemoteScenario(config) {
const { wallet, close, associationUrl } = await (0, _solana_mobile_mobile_wallet_adapter_protocol.startRemoteScenario)(config);
return {
wallet: wallet.then((wallet) => {
return augmentWalletAPI(wallet);
}),
close,
associationUrl
};
}
function augmentWalletAPI(wallet) {
return new Proxy({}, {
get(target, p) {
if (target[p] == null) switch (p) {
case "signAndSendTransactions":
target[p] = async function({ minContextSlot, commitment, skipPreflight, maxRetries, waitForCommitmentToSendNextTransaction, transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const options = {
min_context_slot: minContextSlot,
commitment,
skip_preflight: skipPreflight,
max_retries: maxRetries,
wait_for_commitment_to_send_next_transaction: waitForCommitmentToSendNextTransaction
};
const { signatures: base64EncodedSignatures } = await wallet.signAndSendTransactions({
...rest,
...Object.values(options).some((element) => element != null) ? { options } : null,
payloads
});
return base64EncodedSignatures.map(toUint8Array).map(bs58.default.encode);
};
break;
case "signMessages":
target[p] = async function({ payloads, ...rest }) {
const base64EncodedPayloads = payloads.map(fromUint8Array);
const { signed_payloads: base64EncodedSignedMessages } = await wallet.signMessages({
...rest,
payloads: base64EncodedPayloads
});
return base64EncodedSignedMessages.map(toUint8Array);
};
break;
case "signTransactions":
target[p] = async function({ transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const { signed_payloads: base64EncodedCompiledTransactions } = await wallet.signTransactions({
...rest,
payloads
});
return base64EncodedCompiledTransactions.map(toUint8Array).map(getTransactionFromWireMessage);
};
break;
default:
target[p] = wallet[p];
break;
}
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
//#endregion
exports.startRemoteScenario = startRemoteScenario;
exports.transact = transact;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,117 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _solana_web3_js = require("@solana/web3.js");
let _solana_mobile_mobile_wallet_adapter_protocol = require("@solana-mobile/mobile-wallet-adapter-protocol");
let bs58 = require("bs58");
bs58 = __toESM(bs58);
let js_base64 = require("js-base64");
//#region src/transact.ts
function getPayloadFromTransaction(transaction) {
return (0, js_base64.fromUint8Array)("version" in transaction ? transaction.serialize() : transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
}));
}
function getTransactionFromWireMessage(byteArray) {
const messageOffset = byteArray[0] * _solana_web3_js.SIGNATURE_LENGTH_IN_BYTES + 1;
if (_solana_web3_js.VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return _solana_web3_js.Transaction.from(byteArray);
else return _solana_web3_js.VersionedTransaction.deserialize(byteArray);
}
async function transact(callback, config) {
const augmentedCallback = (wallet) => {
return callback(augmentWalletAPI(wallet));
};
return await (0, _solana_mobile_mobile_wallet_adapter_protocol.transact)(augmentedCallback, config);
}
async function startRemoteScenario(config) {
const { wallet, close, associationUrl } = await (0, _solana_mobile_mobile_wallet_adapter_protocol.startRemoteScenario)(config);
return {
wallet: wallet.then((wallet) => {
return augmentWalletAPI(wallet);
}),
close,
associationUrl
};
}
function augmentWalletAPI(wallet) {
return new Proxy({}, {
get(target, p) {
if (target[p] == null) switch (p) {
case "signAndSendTransactions":
target[p] = async function({ minContextSlot, commitment, skipPreflight, maxRetries, waitForCommitmentToSendNextTransaction, transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const options = {
min_context_slot: minContextSlot,
commitment,
skip_preflight: skipPreflight,
max_retries: maxRetries,
wait_for_commitment_to_send_next_transaction: waitForCommitmentToSendNextTransaction
};
const { signatures: base64EncodedSignatures } = await wallet.signAndSendTransactions({
...rest,
...Object.values(options).some((element) => element != null) ? { options } : null,
payloads
});
return base64EncodedSignatures.map(js_base64.toUint8Array).map(bs58.default.encode);
};
break;
case "signMessages":
target[p] = async function({ payloads, ...rest }) {
const base64EncodedPayloads = payloads.map(js_base64.fromUint8Array);
const { signed_payloads: base64EncodedSignedMessages } = await wallet.signMessages({
...rest,
payloads: base64EncodedPayloads
});
return base64EncodedSignedMessages.map(js_base64.toUint8Array);
};
break;
case "signTransactions":
target[p] = async function({ transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const { signed_payloads: base64EncodedCompiledTransactions } = await wallet.signTransactions({
...rest,
payloads
});
return base64EncodedCompiledTransactions.map(js_base64.toUint8Array).map(getTransactionFromWireMessage);
};
break;
default:
target[p] = wallet[p];
break;
}
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
//#endregion
exports.startRemoteScenario = startRemoteScenario;
exports.transact = transact;
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"commonjs"}

View File

@@ -0,0 +1,99 @@
import { SIGNATURE_LENGTH_IN_BYTES, Transaction, VersionedMessage, VersionedTransaction } from "@solana/web3.js";
import { startRemoteScenario as startRemoteScenario$1, transact as transact$1 } from "@solana-mobile/mobile-wallet-adapter-protocol";
import bs58 from "bs58";
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/transact.ts
function getPayloadFromTransaction(transaction) {
return fromUint8Array("version" in transaction ? transaction.serialize() : transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
}));
}
function getTransactionFromWireMessage(byteArray) {
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return Transaction.from(byteArray);
else return VersionedTransaction.deserialize(byteArray);
}
async function transact(callback, config) {
const augmentedCallback = (wallet) => {
return callback(augmentWalletAPI(wallet));
};
return await transact$1(augmentedCallback, config);
}
async function startRemoteScenario(config) {
const { wallet, close, associationUrl } = await startRemoteScenario$1(config);
return {
wallet: wallet.then((wallet) => {
return augmentWalletAPI(wallet);
}),
close,
associationUrl
};
}
function augmentWalletAPI(wallet) {
return new Proxy({}, {
get(target, p) {
if (target[p] == null) switch (p) {
case "signAndSendTransactions":
target[p] = async function({ minContextSlot, commitment, skipPreflight, maxRetries, waitForCommitmentToSendNextTransaction, transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const options = {
min_context_slot: minContextSlot,
commitment,
skip_preflight: skipPreflight,
max_retries: maxRetries,
wait_for_commitment_to_send_next_transaction: waitForCommitmentToSendNextTransaction
};
const { signatures: base64EncodedSignatures } = await wallet.signAndSendTransactions({
...rest,
...Object.values(options).some((element) => element != null) ? { options } : null,
payloads
});
return base64EncodedSignatures.map(toUint8Array).map(bs58.encode);
};
break;
case "signMessages":
target[p] = async function({ payloads, ...rest }) {
const base64EncodedPayloads = payloads.map(fromUint8Array);
const { signed_payloads: base64EncodedSignedMessages } = await wallet.signMessages({
...rest,
payloads: base64EncodedPayloads
});
return base64EncodedSignedMessages.map(toUint8Array);
};
break;
case "signTransactions":
target[p] = async function({ transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const { signed_payloads: base64EncodedCompiledTransactions } = await wallet.signTransactions({
...rest,
payloads
});
return base64EncodedCompiledTransactions.map(toUint8Array).map(getTransactionFromWireMessage);
};
break;
default:
target[p] = wallet[p];
break;
}
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
//#endregion
export { startRemoteScenario, transact };
//# sourceMappingURL=index.browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,99 @@
import { SIGNATURE_LENGTH_IN_BYTES, Transaction, VersionedMessage, VersionedTransaction } from "@solana/web3.js";
import { startRemoteScenario as startRemoteScenario$1, transact as transact$1 } from "@solana-mobile/mobile-wallet-adapter-protocol";
import bs58 from "bs58";
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/transact.ts
function getPayloadFromTransaction(transaction) {
return fromUint8Array("version" in transaction ? transaction.serialize() : transaction.serialize({
requireAllSignatures: false,
verifySignatures: false
}));
}
function getTransactionFromWireMessage(byteArray) {
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return Transaction.from(byteArray);
else return VersionedTransaction.deserialize(byteArray);
}
async function transact(callback, config) {
const augmentedCallback = (wallet) => {
return callback(augmentWalletAPI(wallet));
};
return await transact$1(augmentedCallback, config);
}
async function startRemoteScenario(config) {
const { wallet, close, associationUrl } = await startRemoteScenario$1(config);
return {
wallet: wallet.then((wallet) => {
return augmentWalletAPI(wallet);
}),
close,
associationUrl
};
}
function augmentWalletAPI(wallet) {
return new Proxy({}, {
get(target, p) {
if (target[p] == null) switch (p) {
case "signAndSendTransactions":
target[p] = async function({ minContextSlot, commitment, skipPreflight, maxRetries, waitForCommitmentToSendNextTransaction, transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const options = {
min_context_slot: minContextSlot,
commitment,
skip_preflight: skipPreflight,
max_retries: maxRetries,
wait_for_commitment_to_send_next_transaction: waitForCommitmentToSendNextTransaction
};
const { signatures: base64EncodedSignatures } = await wallet.signAndSendTransactions({
...rest,
...Object.values(options).some((element) => element != null) ? { options } : null,
payloads
});
return base64EncodedSignatures.map(toUint8Array).map(bs58.encode);
};
break;
case "signMessages":
target[p] = async function({ payloads, ...rest }) {
const base64EncodedPayloads = payloads.map(fromUint8Array);
const { signed_payloads: base64EncodedSignedMessages } = await wallet.signMessages({
...rest,
payloads: base64EncodedPayloads
});
return base64EncodedSignedMessages.map(toUint8Array);
};
break;
case "signTransactions":
target[p] = async function({ transactions, ...rest }) {
const payloads = transactions.map(getPayloadFromTransaction);
const { signed_payloads: base64EncodedCompiledTransactions } = await wallet.signTransactions({
...rest,
payloads
});
return base64EncodedCompiledTransactions.map(toUint8Array).map(getTransactionFromWireMessage);
};
break;
default:
target[p] = wallet[p];
break;
}
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
//#endregion
export { startRemoteScenario, transact };
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"module"}

View File

@@ -0,0 +1,39 @@
import { Transaction, TransactionSignature, VersionedTransaction } from "@solana/web3.js";
import { AuthorizeAPI, Base64EncodedAddress, CloneAuthorizationAPI, DeauthorizeAPI, GetCapabilitiesAPI, ReauthorizeAPI, RemoteWalletAssociationConfig, TerminateSessionAPI, WalletAssociationConfig } from "@solana-mobile/mobile-wallet-adapter-protocol";
//#region src/transact.d.ts
interface Web3SignAndSendTransactionsAPI {
signAndSendTransactions<T extends Transaction | VersionedTransaction>(params: {
minContextSlot?: number;
commitment?: string;
skipPreflight?: boolean;
maxRetries?: number;
waitForCommitmentToSendNextTransaction?: boolean;
transactions: T[];
}): Promise<TransactionSignature[]>;
}
interface Web3SignTransactionsAPI {
signTransactions<T extends Transaction | VersionedTransaction>(params: {
transactions: T[];
}): Promise<T[]>;
}
interface Web3SignMessagesAPI {
signMessages(params: {
addresses: Base64EncodedAddress[];
payloads: Uint8Array[];
}): Promise<Uint8Array[]>;
}
interface Web3MobileWallet extends AuthorizeAPI, CloneAuthorizationAPI, DeauthorizeAPI, GetCapabilitiesAPI, ReauthorizeAPI, Web3SignAndSendTransactionsAPI, Web3SignTransactionsAPI, Web3SignMessagesAPI {}
interface Web3RemoteMobileWallet extends Web3MobileWallet, TerminateSessionAPI {}
type Web3Scenario = Readonly<{
wallet: Promise<Web3MobileWallet>;
close: () => void;
}>;
type Web3RemoteScenario = Web3Scenario & Readonly<{
associationUrl: URL;
}>;
declare function transact<TReturn>(callback: (wallet: Web3MobileWallet) => TReturn, config?: WalletAssociationConfig): Promise<TReturn>;
declare function startRemoteScenario(config: RemoteWalletAssociationConfig): Promise<Web3RemoteScenario>;
//#endregion
export { Web3MobileWallet, Web3RemoteMobileWallet, Web3RemoteScenario, Web3Scenario, startRemoteScenario, transact };
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/transact.ts"],"mappings":";;;;UA2BU,8BAAA;EACN,uBAAA,WAAkC,WAAA,GAAoB,oBAAA,EAAsB,MAAA;IACxE,cAAA;IACA,UAAA;IACA,aAAA;IACA,UAAA;IACA,sCAAA;IACA,YAAA,EAAc,CAAA;EAAA,IACd,OAAA,CAAQ,oBAAA;AAAA;AAAA,UAGN,uBAAA;EACN,gBAAA,WAA2B,WAAA,GAAoB,oBAAA,EAAsB,MAAA;IAAU,YAAA,EAAc,CAAA;EAAA,IAAQ,OAAA,CAAQ,CAAA;AAAA;AAAA,UAGvG,mBAAA;EACN,YAAA,CAAa,MAAA;IAAU,SAAA,EAAW,oBAAA;IAAwB,QAAA,EAAU,UAAA;EAAA,IAAiB,OAAA,CAAQ,UAAA;AAAA;AAAA,UAGhF,gBAAA,SAET,YAAA,EACA,qBAAA,EACA,cAAA,EACA,kBAAA,EACA,cAAA,EACA,8BAAA,EACA,uBAAA,EACA,mBAAA;AAAA,UAES,sBAAA,SAA+B,gBAAA,EAAkB,mBAAA;AAAA,KAEtD,YAAA,GAAe,QAAA;EACvB,MAAA,EAAQ,OAAA,CAAQ,gBAAA;EAChB,KAAA;AAAA;AAAA,KAGQ,kBAAA,GAAqB,YAAA,GAC7B,QAAA;EACI,cAAA,EAAgB,GAAA;AAAA;AAAA,iBA0BF,QAAA,SAAA,CAClB,QAAA,GAAW,MAAA,EAAQ,gBAAA,KAAqB,OAAA,EACxC,MAAA,GAAS,uBAAA,GACV,OAAA,CAAQ,OAAA;AAAA,iBAOW,mBAAA,CAAoB,MAAA,EAAQ,6BAAA,GAAgC,OAAA,CAAQ,kBAAA"}

View File

@@ -0,0 +1,69 @@
{
"name": "@solana-mobile/mobile-wallet-adapter-protocol-web3js",
"description": "A convenience wrapper that enables you to call Solana Mobile Stack protocol methods using objects from @solana/web3.js",
"version": "2.2.8",
"author": "Steven Luscher <steven.luscher@solanamobile.com>",
"repository": {
"type": "git",
"url": "git+https://github.com/solana-mobile/mobile-wallet-adapter.git"
},
"license": "Apache-2.0",
"exports": {
"edge-light": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"workerd": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"browser": {
"import": "./lib/esm/index.browser.js",
"require": "./lib/cjs/index.browser.js"
},
"node": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"react-native": "./lib/cjs/index.native.js",
"types": "./lib/types/index.d.ts"
},
"browser": {
"./lib/cjs/index.js": "./lib/cjs/index.browser.js",
"./lib/esm/index.js": "./lib/esm/index.browser.js"
},
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"react-native": "lib/cjs/index.native.js",
"types": "lib/types/index.d.ts",
"type": "module",
"files": [
"lib",
"LICENSE"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@solana/web3.js": "^1.98.4"
},
"dependencies": {
"bs58": "^6.0.0",
"js-base64": "^3.7.5",
"@solana-mobile/mobile-wallet-adapter-protocol": "^2.2.8"
},
"devDependencies": {
"@solana/web3.js": "^1.98.4",
"agadoo": "^3.0.0",
"cross-env": "^10.1.0",
"shx": "^0.4.0"
},
"scripts": {
"clean": "shx rm -rf lib/*",
"build": "pnpm clean && tsdown --config ../../tsdown.config.ts",
"build:watch": "pnpm clean && tsdown --config ../../tsdown.config.ts --watch",
"check-types": "tsc -p tsconfig.json --noEmit",
"postbuild": "printf '%s' '{\"type\":\"commonjs\"}' > lib/cjs/package.json && printf '%s' '{\"type\":\"module\"}' > lib/esm/package.json"
}
}

View File

@@ -0,0 +1,13 @@
Copyright 2022 Solana Mobile Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,73 @@
# `@solana-mobile/mobile-wallet-adapter-protocol`
This is a reference implementation of the [Mobile Wallet Adapter specification](https://github.com/solana-mobile/mobile-wallet-adapter/blob/main/spec/spec.md) in JavaScript. Use this to start a session with a mobile wallet in which you can issue API calls to it (eg. `sign_messages`) as per the spec.
If you are simply looking to integrate a JavaScript application with mobile wallets, see [`@solana-mobile/wallet-adapter-mobile`](https://www.npmjs.com/package/@solana-mobile/wallet-adapter-mobile) instead.
## Learn how to use this API on our [documentation website](https://docs.solanamobile.com/):
- React Native
- [Quickstart Setup](https://docs.solanamobile.com/react-native/quickstart)
- [dApp Integration Guide](https://docs.solanamobile.com/react-native/mwa_integration_rn)
- [Hello World Tutorial](https://docs.solanamobile.com/getting-started/hello_world_tutorial)
- [Sample App Reference](https://docs.solanamobile.com/sample-apps/sample_app_overview)
## Quick start
Use this API to start a session:
```typescript
import { transact } from '@solana-mobile/mobile-wallet-adapter-protocol';
await transact(async (wallet) => {
/* ... */
});
```
The callback you provide will be called once a session has been established with a wallet. It will recieve the `MobileWallet` API as an argument. You can call protocol-specified methods using this function. Whatever you return from this callback will be returned by `transact`.
```typescript
const signedPayloads = await transact(async (wallet) => {
const { signed_payloads } = await wallet.signMessages({
auth_token,
payloads: [
/* ... */
],
});
return signed_payloads;
});
```
The wallet session will stay active until your callback returns. Typically, wallets will redirect back to your app once the session ends.
## Exception handling
You can catch exceptions at any level. See `errors.ts` for a list of exceptions that might be thrown.
```typescript
try {
await transact(async (wallet) => {
try {
await wallet.signTransactions(/* ... */);
} catch (e) {
if (
e instanceof SolanaMobileWalletAdapterProtocolError &&
e.code === SolanaMobileWalletAdapterProtocolErrorCode.ERROR_REAUTHORIZE
) {
console.error('The auth token has gone stale');
await wallet.reauthorize({ auth_token, identity });
// Retry...
}
throw e;
}
});
} catch (e) {
if (
e instanceof SolanaMobileWalletAdapterError &&
e.code === SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND
) {
/* ... */
}
throw e;
}
```

View File

@@ -0,0 +1,158 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['SolanaMobileWalletAdapterModule_kotlinVersion']
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:9.0.1'
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
if (isNewArchitectureEnabled()) {
apply plugin: 'com.facebook.react'
}
def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['SolanaMobileWalletAdapterModule_' + name]
}
def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['SolanaMobileWalletAdapterModule_' + name]).toInteger()
}
android {
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
defaultConfig {
minSdkVersion getExtOrIntegerDefault('minSdkVersion')
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
sourceSets {
main {
if (isNewArchitectureEnabled()) {
java.srcDirs += [
"src/newarch"
]
} else {
java.srcDirs += ["src/oldarch"]
}
}
}
}
repositories {
mavenCentral()
google()
def found = false
def defaultDir = null
def androidSourcesName = 'React Native sources'
if (rootProject.ext.has('reactNativeAndroidRoot')) {
defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
} else {
defaultDir = new File(
projectDir,
'/../../../node_modules/react-native/android'
)
}
if (defaultDir.exists()) {
maven {
url defaultDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
found = true
} else {
def parentDir = rootProject.projectDir
1.upto(5, {
if (found) return true
parentDir = parentDir.parentFile
def androidSourcesDir = new File(
parentDir,
'node_modules/react-native'
)
def androidPrebuiltBinaryDir = new File(
parentDir,
'node_modules/react-native/android'
)
if (androidPrebuiltBinaryDir.exists()) {
maven {
url androidPrebuiltBinaryDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
found = true
} else if (androidSourcesDir.exists()) {
maven {
url androidSourcesDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
found = true
}
})
}
if (!found) {
throw new GradleException(
"${project.name}: unable to locate React Native android sources. " +
"Ensure you have you installed React Native as a dependency in your project and try again."
)
}
}
def kotlin_version = getExtOrDefault('kotlinVersion')
dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "com.solanamobile:mobile-wallet-adapter-clientlib:2.1.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
}
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "SolanaMobileWalletAdapterModule"
codegenJavaPackageName = "com.solanamobile.mobilewalletadapter.reactnative"
}
}

View File

@@ -0,0 +1,5 @@
SolanaMobileWalletAdapterModule_kotlinVersion=1.9.0
SolanaMobileWalletAdapterModule_minSdkVersion=21
SolanaMobileWalletAdapterModule_targetSdkVersion=33
SolanaMobileWalletAdapterModule_compileSdkVersion=33
SolanaMobileWalletAdapterModule_ndkversion=23.1.7779620

View File

@@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.solanamobile.mobilewalletadapter.reactnative">
</manifest>

View File

@@ -0,0 +1,84 @@
package com.solanamobile.mobilewalletadapter.reactnative
import com.facebook.react.bridge.*
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
object JSONSerializationUtils {
@Throws(JSONException::class)
fun convertMapToJson(readableMap: ReadableMap?): JSONObject {
val json = JSONObject()
readableMap?.keySetIterator()?.let { iterator ->
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
when (readableMap.getType(key)) {
ReadableType.Array -> json.put(
key,
readableMap.getArray(key)?.let { convertArrayToJson(it) }
)
ReadableType.Boolean -> json.put(key, readableMap.getBoolean(key))
ReadableType.Map -> json.put(key, convertMapToJson(readableMap.getMap(key)))
ReadableType.Null -> json.put(key, JSONObject.NULL)
ReadableType.Number -> json.put(key, readableMap.getDouble(key))
ReadableType.String -> json.put(key, readableMap.getString(key))
}
}
}
return json
}
@Throws(JSONException::class)
private fun convertArrayToJson(readableArray: ReadableArray?): JSONArray {
val array = JSONArray()
readableArray?.let {
for (i in 0 until readableArray.size()) {
when (readableArray.getType(i)) {
ReadableType.Array -> array.put(convertArrayToJson(readableArray.getArray(i)))
ReadableType.Boolean -> array.put(readableArray.getBoolean(i))
ReadableType.Map -> array.put(convertMapToJson(readableArray.getMap(i)))
ReadableType.Null -> {}
ReadableType.Number -> array.put(readableArray.getDouble(i))
ReadableType.String -> array.put(readableArray.getString(i))
}
}
}
return array
}
@Throws(JSONException::class)
fun convertJsonToMap(jsonObject: JSONObject): ReadableMap {
val map: WritableMap = WritableNativeMap()
val iterator = jsonObject.keys()
while (iterator.hasNext()) {
val key = iterator.next()
when (val value = jsonObject[key]) {
is Boolean -> map.putBoolean(key, value)
is Double -> map.putDouble(key, value)
is Int -> map.putInt(key, value)
is JSONArray -> map.putArray(key, convertJsonToArray(value))
is JSONObject -> map.putMap(key, convertJsonToMap(value))
is String -> map.putString(key, value)
else -> map.putString(key, value.toString())
}
}
return map
}
@Throws(JSONException::class)
private fun convertJsonToArray(jsonArray: JSONArray): ReadableArray {
val array: WritableArray = WritableNativeArray()
for (i in 0 until jsonArray.length()) {
when (val value = jsonArray[i]) {
is Boolean -> array.pushBoolean(value)
is Double -> array.pushDouble(value)
is Int -> array.pushInt(value)
is JSONArray -> array.pushArray(convertJsonToArray(value))
is JSONObject -> array.pushMap(convertJsonToMap(value))
is String -> array.pushString(value)
else -> array.pushString(value.toString())
}
}
return array
}
}

View File

@@ -0,0 +1,233 @@
package com.solanamobile.mobilewalletadapter.reactnative
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.util.Log
import com.facebook.react.bridge.*
import com.facebook.react.jstasks.HeadlessJsTaskConfig
import com.facebook.react.jstasks.HeadlessJsTaskContext
import com.solana.mobilewalletadapter.clientlib.protocol.JsonRpc20Client
import com.solana.mobilewalletadapter.clientlib.protocol.MobileWalletAdapterClient
import com.solana.mobilewalletadapter.clientlib.scenario.LocalAssociationIntentCreator
import com.solana.mobilewalletadapter.clientlib.scenario.LocalAssociationScenario
import com.solana.mobilewalletadapter.common.protocol.SessionProperties.ProtocolVersion
import com.solanamobile.mobilewalletadapter.reactnative.JSONSerializationUtils.convertJsonToMap
import com.solanamobile.mobilewalletadapter.reactnative.JSONSerializationUtils.convertMapToJson
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import org.json.JSONObject
class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
SolanaMobileWalletAdapterSpec(reactContext), CoroutineScope {
data class SessionState(
val client: MobileWalletAdapterClient,
val localAssociation: LocalAssociationScenario,
)
override val coroutineContext =
Dispatchers.IO + CoroutineName("SolanaMobileWalletAdapterModuleScope") + SupervisorJob()
companion object {
const val NAME = "SolanaMobileWalletAdapter"
private const val TAG = "SolanaMobileWalletAdapterModule"
private const val ASSOCIATION_TIMEOUT_MS = 10000
private const val CLIENT_TIMEOUT_MS = 90000
private const val REQUEST_LOCAL_ASSOCIATION = 0
}
// Used to ensure that you can't start more than one session at a time.
private val mutex: Mutex = Mutex()
private var sessionState: SessionState? = null
private var associationResultCallback: ((Int) -> Unit)? = null
private val mActivityEventListener: ActivityEventListener =
object : BaseActivityEventListener() {
override fun onActivityResult(
activity: Activity,
requestCode: Int,
resultCode: Int,
data: Intent?
) {
if (requestCode == REQUEST_LOCAL_ASSOCIATION)
associationResultCallback?.invoke(resultCode)
}
}
private val sessionBackgroundTaskConfig
get() = HeadlessJsTaskConfig(
"SolanaMobileWalletAdapterSessionBackgroundTask",
Arguments.createMap(),
0,
true
)
init {
reactContext.addActivityEventListener(mActivityEventListener)
}
override fun getName(): String {
return NAME
}
@ReactMethod
override fun startSession(config: ReadableMap?, promise: Promise): Unit {
launch {
mutex.lock()
Log.d(TAG, "startSession with config $config")
var sessionTaskId: Int? = null
val headlessJsTaskContext = HeadlessJsTaskContext.getInstance(reactApplicationContext)
val finishHeadlessTask = { taskId: Int? ->
try {
if (taskId != null && headlessJsTaskContext.isTaskRunning(taskId)) {
headlessJsTaskContext.finishTask(taskId)
}
// fix for Expo 52/RN 0.72/0.73 where the older kotlin/gradle toolchain complains
// about the above if statement being used as an expression. Explicitly returning
// Unit here tells the compiler that the above if is not an expression
Unit
} catch (e: Exception) {
Log.w(TAG, "Failed to finish headless JS task", e)
}
}
try {
val uriPrefix = config?.getString("baseUri")?.let { Uri.parse(it) }
val localAssociation =
LocalAssociationScenario(
CLIENT_TIMEOUT_MS,
)
val intent =
LocalAssociationIntentCreator.createAssociationIntent(
uriPrefix,
localAssociation.port,
localAssociation.session
)
withContext(Dispatchers.Main) {
sessionTaskId = headlessJsTaskContext.startTask(sessionBackgroundTaskConfig)
}
associationResultCallback = { resultCode ->
if (resultCode == Activity.RESULT_CANCELED) {
Log.d(TAG, "Local association cancelled by user, ending session")
promise.reject(
"Session not established: Local association cancelled by user",
LocalAssociationScenario.ConnectionFailedException(
"Local association cancelled by user"
)
)
localAssociation.close()
}
// stop the headless js task, regardless if the association was successful or not
finishHeadlessTask(sessionTaskId)
}
reactApplicationContext.currentActivity?.apply {
startActivityForResult(intent, REQUEST_LOCAL_ASSOCIATION)
} ?: throw NullPointerException(
"Could not find a current activity from which to launch a local association"
)
val client = localAssociation.start()
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
sessionState = SessionState(client, localAssociation)
val sessionPropertiesMap: WritableMap = WritableNativeMap()
sessionPropertiesMap.putString(
"protocol_version",
when (localAssociation.session.sessionProperties.protocolVersion) {
ProtocolVersion.LEGACY -> "legacy"
ProtocolVersion.V1 -> "v1"
}
)
promise.resolve(sessionPropertiesMap)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "Found no installed wallet that supports the mobile wallet protocol", e)
finishHeadlessTask(sessionTaskId)
cleanup()
promise.reject("ERROR_WALLET_NOT_FOUND", e)
} catch (e: TimeoutException) {
Log.e(TAG, "Timed out waiting for local association to be ready", e)
finishHeadlessTask(sessionTaskId)
cleanup()
promise.reject("Timed out waiting for local association to be ready", e)
} catch (e: InterruptedException) {
Log.w(TAG, "Interrupted while waiting for local association to be ready", e)
finishHeadlessTask(sessionTaskId)
cleanup()
promise.reject(e)
} catch (e: ExecutionException) {
Log.e(TAG, "Failed establishing local association with wallet", e.cause)
finishHeadlessTask(sessionTaskId)
cleanup()
promise.reject(e)
} catch (e: Throwable) {
Log.e(TAG, "Failed to start session", e)
finishHeadlessTask(sessionTaskId)
cleanup()
promise.reject(e)
}
}
}
@ReactMethod
override fun invoke(method: String, params: ReadableMap?, promise: Promise): Unit =
sessionState?.let {
Log.d(TAG, "invoke `$method` with params $params")
try {
val result = it.client
.methodCall(method, convertMapToJson(params), CLIENT_TIMEOUT_MS)
.get() as JSONObject
promise.resolve(convertJsonToMap(result))
} catch (e: ExecutionException) {
val cause = e.cause
if (cause is JsonRpc20Client.JsonRpc20RemoteException) {
val userInfo = Arguments.createMap()
userInfo.putInt("jsonRpcErrorCode", cause.code)
promise.reject("JSON_RPC_ERROR", cause, userInfo)
} else if (cause is TimeoutException) {
promise.reject("Timed out waiting for response", e)
} else {
throw e
}
} catch (e: Throwable) {
Log.e(TAG, "Failed to invoke `$method` with params $params", e)
promise.reject(e)
}
} ?: throw NullPointerException(
"Tried to invoke `$method` without an active session"
)
@ReactMethod
override fun endSession(promise: Promise): Unit {
sessionState?.let {
launch {
Log.d(TAG, "endSession")
try {
it.localAssociation
.close()
.get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
cleanup()
promise.resolve(true)
} catch (e: TimeoutException) {
Log.e(TAG, "Timed out waiting for local association to close", e)
cleanup()
promise.reject("Failed to end session", e)
} catch (e: Throwable) {
Log.e(TAG, "Failed to end session", e)
cleanup()
promise.reject("Failed to end session", e)
}
}
} ?: throw NullPointerException("Tried to end a session without an active session")
}
private fun cleanup() {
sessionState = null
associationResultCallback = null
if (mutex.isLocked) {
mutex.unlock()
}
}
}

View File

@@ -0,0 +1,35 @@
package com.solanamobile.mobilewalletadapter.reactnative
import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import java.util.HashMap
class SolanaMobileWalletAdapterModulePackage : TurboReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == SolanaMobileWalletAdapterModule.NAME) {
SolanaMobileWalletAdapterModule(reactContext)
} else {
null
}
}
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
moduleInfos[SolanaMobileWalletAdapterModule.NAME] =
ReactModuleInfo(
SolanaMobileWalletAdapterModule.NAME,
SolanaMobileWalletAdapterModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
true // isTurboModule
)
moduleInfos
}
}
}

View File

@@ -0,0 +1,7 @@
package com.solanamobile.mobilewalletadapter.reactnative
import com.facebook.react.bridge.ReactApplicationContext
abstract class SolanaMobileWalletAdapterSpec
internal constructor(context: ReactApplicationContext) :
NativeSolanaMobileWalletAdapterSpec(context) {}

View File

@@ -0,0 +1,16 @@
package com.solanamobile.mobilewalletadapter.reactnative
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReadableMap
abstract class SolanaMobileWalletAdapterSpec
internal constructor(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
abstract fun startSession(config: ReadableMap?, promise: Promise)
abstract fun invoke(method: String, params: ReadableMap?, promise: Promise)
abstract fun endSession(promise: Promise)
}

View File

@@ -0,0 +1,924 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let _solana_codecs_strings = require("@solana/codecs-strings");
let _solana_wallet_standard_util = require("@solana/wallet-standard-util");
//#region src/errors.ts
const SolanaMobileWalletAdapterErrorCode = {
ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE",
ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE",
ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL",
ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED",
ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED",
ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT",
ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND",
ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
};
var SolanaMobileWalletAdapterError = class extends Error {
data;
code;
constructor(...args) {
const [code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.name = "SolanaMobileWalletAdapterError";
}
};
const SolanaMobileWalletAdapterProtocolErrorCode = {
ERROR_AUTHORIZATION_FAILED: -1,
ERROR_INVALID_PAYLOADS: -2,
ERROR_NOT_SIGNED: -3,
ERROR_NOT_SUBMITTED: -4,
ERROR_TOO_MANY_PAYLOADS: -5,
ERROR_ATTEST_ORIGIN_ANDROID: -100
};
var SolanaMobileWalletAdapterProtocolError = class extends Error {
data;
code;
jsonRpcMessageId;
constructor(...args) {
const [jsonRpcMessageId, code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.jsonRpcMessageId = jsonRpcMessageId;
this.name = "SolanaMobileWalletAdapterProtocolError";
}
};
//#endregion
//#region src/base64Utils.ts
function encode(input) {
return window.btoa(input);
}
function fromUint8Array$1(byteArray, urlsafe) {
const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
if (urlsafe) return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
else return base64;
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/createHelloReq.ts
async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
const publicKeyBuffer = await crypto.subtle.exportKey("raw", ecdhPublicKey);
const signatureBuffer = await crypto.subtle.sign({
hash: "SHA-256",
name: "ECDSA"
}, associationKeypairPrivateKey, publicKeyBuffer);
const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
response.set(new Uint8Array(publicKeyBuffer), 0);
response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
return response;
}
//#endregion
//#region src/base58Utils.ts
function fromUint8Array(byteArray) {
return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
}
function base64ToBase58(base64EncodedString) {
return fromUint8Array(toUint8Array(base64EncodedString));
}
//#endregion
//#region src/createSIWSMessage.ts
function createSIWSMessage(payload) {
return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
}
function createSIWSMessageBase64Url(payload) {
return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
//#endregion
//#region src/types.ts
const SolanaSignTransactions = "solana:signTransactions";
const SolanaCloneAuthorization = "solana:cloneAuthorization";
const SolanaSignInWithSolana = "solana:signInWithSolana";
//#endregion
//#region src/createMobileWalletProxy.ts
/**
* Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
*
* @param protocolVersion the protocol version in use for this session/request
* @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
* @returns a {@link MobileWallet} proxy
*/
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
return new Proxy({}, {
get(target, p) {
if (p === "then") return null;
if (target[p] == null) target[p] = async function(inputParams) {
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
const result = await protocolRequestHandler(method, params);
if (method === "authorize" && params.sign_in_payload && !result.sign_in_result) result.sign_in_result = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
return handleMobileWalletResponse(p, result, protocolVersion);
};
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
/**
* Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
* This handles backwards compatibility, based on the provided @protocolVersion.
*
* @param methodName the name of {@link MobileWallet} method that was called
* @param methodParams the parameters that were passed to the method
* @param protocolVersion the protocol version in use for this session/request
* @returns the RPC request method and params that should be sent to the wallet endpoint
*/
function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
let params = methodParams;
let method = methodName.toString().replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toLowerCase();
switch (methodName) {
case "authorize": {
const authorizeParams = params;
let { chain } = authorizeParams;
if (protocolVersion === "legacy") {
switch (chain) {
case "solana:testnet":
chain = "testnet";
break;
case "solana:devnet":
chain = "devnet";
break;
case "solana:mainnet":
chain = "mainnet-beta";
break;
default: chain = authorizeParams.cluster;
}
authorizeParams.cluster = chain;
params = authorizeParams;
} else {
switch (chain) {
case "testnet":
case "devnet":
chain = `solana:${chain}`;
break;
case "mainnet-beta":
chain = "solana:mainnet";
break;
}
authorizeParams.chain = chain;
params = authorizeParams;
}
}
case "reauthorize": {
const { auth_token, identity } = params;
if (auth_token) switch (protocolVersion) {
case "legacy":
method = "reauthorize";
params = {
auth_token,
identity
};
break;
default:
method = "authorize";
break;
}
break;
}
}
return {
method,
params
};
}
/**
* Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
*
* @param method the {@link MobileWallet} method that was called
* @param response the original response that was returned by the method call
* @param protocolVersion the protocol version in use for this session/request
* @returns the possibly modified response
*/
function handleMobileWalletResponse(method, response, protocolVersion) {
switch (method) {
case "getCapabilities": {
const capabilities = response;
switch (protocolVersion) {
case "legacy": {
const features = [SolanaSignTransactions];
if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
return {
...capabilities,
features
};
}
case "v1": return {
...capabilities,
supports_sign_and_send_transactions: true,
supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
};
}
}
}
return response;
}
async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
const domain = signInPayload.domain ?? window.location.host;
const address = authorizationResult.accounts[0].address;
const siwsMessage = createSIWSMessageBase64Url({
...signInPayload,
domain,
address: base64ToBase58(address)
});
const signedPayload = toUint8Array((await protocolRequestHandler("sign_messages", {
addresses: [address],
payloads: [siwsMessage]
})).signed_payloads[0]);
const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
return {
address,
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
signature
};
}
function createSequenceNumberVector(sequenceNumber) {
if (sequenceNumber >= 4294967296) throw new Error("Outbound sequence number overflow. The maximum sequence number is 32-bytes.");
const byteArray = /* @__PURE__ */ new ArrayBuffer(4);
new DataView(byteArray).setUint32(0, sequenceNumber, false);
return new Uint8Array(byteArray);
}
//#endregion
//#region src/encryptedMessage.ts
const INITIALIZATION_VECTOR_BYTES = 12;
async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
crypto.getRandomValues(initializationVector);
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
response.set(new Uint8Array(sequenceNumberVector), 0);
response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
return response;
}
async function decryptMessage(message, sharedSecret) {
const sequenceNumberVector = message.slice(0, 4);
const initializationVector = message.slice(4, 4 + INITIALIZATION_VECTOR_BYTES);
const ciphertext = message.slice(4 + INITIALIZATION_VECTOR_BYTES);
const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
return getUtf8Decoder().decode(plaintextBuffer);
}
function getAlgorithmParams(sequenceNumber, initializationVector) {
return {
additionalData: sequenceNumber,
iv: initializationVector,
name: "AES-GCM",
tagLength: 128
};
}
let _utf8Decoder;
function getUtf8Decoder() {
if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
return _utf8Decoder;
}
//#endregion
//#region src/generateAssociationKeypair.ts
async function generateAssociationKeypair() {
return await crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-256"
}, false, ["sign"]);
}
//#endregion
//#region src/generateECDHKeypair.ts
async function generateECDHKeypair() {
return await crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-256"
}, false, ["deriveKey", "deriveBits"]);
}
//#endregion
//#region src/arrayBufferToBase64String.ts
function arrayBufferToBase64String(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
return window.btoa(binary);
}
//#endregion
//#region src/associationPort.ts
function getRandomAssociationPort() {
return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
}
function assertAssociationPort(port) {
if (port < 49152 || port > 65535) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
return port;
}
//#endregion
//#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
"/": "_",
"+": "-",
"=": "."
})[m]);
}
//#endregion
//#region src/getAssociateAndroidIntentURL.ts
const INTENT_NAME = "solana-wallet";
function getPathParts(pathString) {
return pathString.replace(/(^\/+|\/+$)/g, "").split("/");
}
function getIntentURL(methodPathname, intentUrlBase) {
let baseUrl = null;
if (intentUrlBase) {
try {
baseUrl = new URL(intentUrlBase);
} catch {}
if (baseUrl?.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
baseUrl ||= new URL(`${INTENT_NAME}:/`);
const pathname = methodPathname.startsWith("/") ? methodPathname : [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join("/");
return new URL(pathname, baseUrl);
}
async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
const associationPort = assertAssociationPort(putativePort);
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/local", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("port", `${associationPort}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/remote", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("reflector", `${hostAuthority}`);
url.searchParams.set("id", `${fromUint8Array$1(reflectorId, true)}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
//#endregion
//#region src/jsonRpcMessage.ts
async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
const plaintext = JSON.stringify(jsonRpcMessage);
const sequenceNumber = jsonRpcMessage.id;
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
}
async function decryptJsonRpcMessage(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonRpcMessage = JSON.parse(plaintext);
if (Object.hasOwnProperty.call(jsonRpcMessage, "error")) throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
return jsonRpcMessage;
}
//#endregion
//#region src/parseHelloRsp.ts
async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey) {
const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([crypto.subtle.exportKey("raw", associationPublicKey), crypto.subtle.importKey("raw", payloadBuffer.slice(0, 65), {
name: "ECDH",
namedCurve: "P-256"
}, false, [])]);
const sharedSecret = await crypto.subtle.deriveBits({
name: "ECDH",
public: walletPublicKey
}, ecdhPrivateKey, 256);
const ecdhSecretKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
return await crypto.subtle.deriveKey({
name: "HKDF",
hash: "SHA-256",
salt: new Uint8Array(associationPublicKeyBuffer),
info: new Uint8Array()
}, ecdhSecretKey, {
name: "AES-GCM",
length: 128
}, false, ["encrypt", "decrypt"]);
}
//#endregion
//#region src/parseSessionProps.ts
async function parseSessionProps(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonProperties = JSON.parse(plaintext);
let protocolVersion = "legacy";
if (Object.hasOwnProperty.call(jsonProperties, "v")) switch (jsonProperties.v) {
case 1:
case "1":
case "v1":
protocolVersion = "v1";
break;
case "legacy":
protocolVersion = "legacy";
break;
default: throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
}
return { protocol_version: protocolVersion };
}
//#endregion
//#region src/startSession.ts
const Browser = {
Firefox: 0,
Other: 1
};
function assertUnreachable(x) {
return x;
}
function getBrowser() {
return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
}
function getDetectionPromise() {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeoutId);
window.removeEventListener("blur", handleBlur);
}
function handleBlur() {
cleanup();
resolve();
}
window.addEventListener("blur", handleBlur);
const timeoutId = setTimeout(() => {
cleanup();
reject();
}, 3e3);
});
}
let _frame = null;
function launchUrlThroughHiddenFrame(url) {
if (_frame == null) {
_frame = document.createElement("iframe");
_frame.style.display = "none";
document.body.appendChild(_frame);
}
_frame.contentWindow.location.href = url.toString();
}
async function launchAssociation(associationUrl) {
if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
else try {
const browser = getBrowser();
switch (browser) {
case Browser.Firefox:
launchUrlThroughHiddenFrame(associationUrl);
break;
case Browser.Other: {
const detectionPromise = getDetectionPromise();
window.location.assign(associationUrl);
await detectionPromise;
break;
}
default: assertUnreachable(browser);
}
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
}
}
async function startSession(associationPublicKey, associationURLBase) {
const randomAssociationPort = getRandomAssociationPort();
await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
return randomAssociationPort;
}
//#endregion
//#region src/transact.ts
const WEBSOCKET_CONNECTION_CONFIG = {
retryDelayScheduleMs: [
150,
150,
200,
500,
500,
750,
750,
1e3
],
timeoutMs: 3e4
};
const WEBSOCKET_PROTOCOL_BINARY = "com.solana.mobilewalletadapter.v1";
const WEBSOCKET_PROTOCOL_BASE64 = "com.solana.mobilewalletadapter.v1.base64";
function assertSecureContext() {
if (typeof window === "undefined" || window.isSecureContext !== true) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, "The mobile wallet adapter protocol must be used in a secure context (`https`).");
}
function assertSecureEndpointSpecificURI(walletUriBase) {
let url;
try {
url = new URL(walletUriBase);
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Invalid base URL supplied by wallet");
}
if (url.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
function getSequenceNumberFromByteArray(byteArray) {
return new DataView(byteArray).getUint32(0, false);
}
function decodeVarLong(byteArray) {
const bytes = new Uint8Array(byteArray);
const l = byteArray.byteLength;
const limit = 10;
let value = 0, offset = 0, b;
do {
if (offset >= l || offset > limit) throw new RangeError("Failed to decode varint");
b = bytes[offset++];
value |= (b & 127) << 7 * offset;
} while (b >= 128);
return {
value,
offset
};
}
function getReflectorIdFromByteArray(byteArray) {
const { value: length, offset } = decodeVarLong(byteArray);
return new Uint8Array(byteArray.slice(offset, offset + length));
}
async function transact(callback, config) {
const { wallet, close } = await startScenario(config);
try {
return await callback(await wallet);
} finally {
close();
}
}
async function startScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `ws://localhost:${await startSession(associationKeypair.publicKey, config?.baseUri)}/solana-wallet`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let state = { __type: "disconnected" };
let socket;
let sessionEstablished = false;
let handleForceClose;
return {
close: () => {
socket.close();
handleForceClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
socket.removeEventListener("open", handleOpen);
const { associationKeypair } = state;
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleMessage = async (evt) => {
const responseBuffer = await evt.data.arrayBuffer();
switch (state.__type) {
case "connecting": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while connecting");
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
if (responseBuffer.byteLength === 0) {
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
socket.send(await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret));
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
handleForceClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
let disposeSocket;
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleMessage);
};
};
attemptSocketConnection();
})
};
}
async function startRemoteScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let encoding;
let state = { __type: "disconnected" };
let socket;
let disposeSocket;
const decodeBytes = async (evt) => {
if (encoding == "base64") return toUint8Array(await evt.data).buffer;
else return await evt.data.arrayBuffer();
};
const associationUrl = await new Promise((resolve, reject) => {
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) encoding = "base64";
else encoding = "binary";
socket.removeEventListener("open", handleOpen);
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleReflectorIdMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
if (state.__type === "connecting") {
if (responseBuffer.byteLength == 0) throw new Error("Encountered unexpected message while connecting");
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
state = {
__type: "reflector_id_received",
reflectorId
};
const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
socket.removeEventListener("message", handleReflectorIdMessage);
resolve(associationUrl);
}
};
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleReflectorIdMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleReflectorIdMessage);
};
};
attemptSocketConnection();
});
let sessionEstablished = false;
let handleClose;
return {
associationUrl,
close: () => {
socket.close();
handleClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
switch (state.__type) {
case "reflector_id_received": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while awaiting reflection");
const ecdhKeypair = await generateECDHKeypair();
const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
const binaryMsg = await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
socket.addEventListener("message", handleMessage);
handleClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
})
};
}
//#endregion
exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
exports.SolanaSignTransactions = SolanaSignTransactions;
exports.startRemoteScenario = startRemoteScenario;
exports.startScenario = startScenario;
exports.transact = transact;
//# sourceMappingURL=index.browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,924 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let _solana_codecs_strings = require("@solana/codecs-strings");
let _solana_wallet_standard_util = require("@solana/wallet-standard-util");
//#region src/errors.ts
const SolanaMobileWalletAdapterErrorCode = {
ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE",
ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE",
ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL",
ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED",
ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED",
ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT",
ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND",
ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
};
var SolanaMobileWalletAdapterError = class extends Error {
data;
code;
constructor(...args) {
const [code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.name = "SolanaMobileWalletAdapterError";
}
};
const SolanaMobileWalletAdapterProtocolErrorCode = {
ERROR_AUTHORIZATION_FAILED: -1,
ERROR_INVALID_PAYLOADS: -2,
ERROR_NOT_SIGNED: -3,
ERROR_NOT_SUBMITTED: -4,
ERROR_TOO_MANY_PAYLOADS: -5,
ERROR_ATTEST_ORIGIN_ANDROID: -100
};
var SolanaMobileWalletAdapterProtocolError = class extends Error {
data;
code;
jsonRpcMessageId;
constructor(...args) {
const [jsonRpcMessageId, code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.jsonRpcMessageId = jsonRpcMessageId;
this.name = "SolanaMobileWalletAdapterProtocolError";
}
};
//#endregion
//#region src/base64Utils.ts
function encode(input) {
return window.btoa(input);
}
function fromUint8Array$1(byteArray, urlsafe) {
const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
if (urlsafe) return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
else return base64;
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/createHelloReq.ts
async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
const publicKeyBuffer = await crypto.subtle.exportKey("raw", ecdhPublicKey);
const signatureBuffer = await crypto.subtle.sign({
hash: "SHA-256",
name: "ECDSA"
}, associationKeypairPrivateKey, publicKeyBuffer);
const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
response.set(new Uint8Array(publicKeyBuffer), 0);
response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
return response;
}
//#endregion
//#region src/base58Utils.ts
function fromUint8Array(byteArray) {
return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
}
function base64ToBase58(base64EncodedString) {
return fromUint8Array(toUint8Array(base64EncodedString));
}
//#endregion
//#region src/createSIWSMessage.ts
function createSIWSMessage(payload) {
return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
}
function createSIWSMessageBase64Url(payload) {
return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
//#endregion
//#region src/types.ts
const SolanaSignTransactions = "solana:signTransactions";
const SolanaCloneAuthorization = "solana:cloneAuthorization";
const SolanaSignInWithSolana = "solana:signInWithSolana";
//#endregion
//#region src/createMobileWalletProxy.ts
/**
* Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
*
* @param protocolVersion the protocol version in use for this session/request
* @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
* @returns a {@link MobileWallet} proxy
*/
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
return new Proxy({}, {
get(target, p) {
if (p === "then") return null;
if (target[p] == null) target[p] = async function(inputParams) {
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
const result = await protocolRequestHandler(method, params);
if (method === "authorize" && params.sign_in_payload && !result.sign_in_result) result.sign_in_result = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
return handleMobileWalletResponse(p, result, protocolVersion);
};
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
/**
* Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
* This handles backwards compatibility, based on the provided @protocolVersion.
*
* @param methodName the name of {@link MobileWallet} method that was called
* @param methodParams the parameters that were passed to the method
* @param protocolVersion the protocol version in use for this session/request
* @returns the RPC request method and params that should be sent to the wallet endpoint
*/
function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
let params = methodParams;
let method = methodName.toString().replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toLowerCase();
switch (methodName) {
case "authorize": {
const authorizeParams = params;
let { chain } = authorizeParams;
if (protocolVersion === "legacy") {
switch (chain) {
case "solana:testnet":
chain = "testnet";
break;
case "solana:devnet":
chain = "devnet";
break;
case "solana:mainnet":
chain = "mainnet-beta";
break;
default: chain = authorizeParams.cluster;
}
authorizeParams.cluster = chain;
params = authorizeParams;
} else {
switch (chain) {
case "testnet":
case "devnet":
chain = `solana:${chain}`;
break;
case "mainnet-beta":
chain = "solana:mainnet";
break;
}
authorizeParams.chain = chain;
params = authorizeParams;
}
}
case "reauthorize": {
const { auth_token, identity } = params;
if (auth_token) switch (protocolVersion) {
case "legacy":
method = "reauthorize";
params = {
auth_token,
identity
};
break;
default:
method = "authorize";
break;
}
break;
}
}
return {
method,
params
};
}
/**
* Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
*
* @param method the {@link MobileWallet} method that was called
* @param response the original response that was returned by the method call
* @param protocolVersion the protocol version in use for this session/request
* @returns the possibly modified response
*/
function handleMobileWalletResponse(method, response, protocolVersion) {
switch (method) {
case "getCapabilities": {
const capabilities = response;
switch (protocolVersion) {
case "legacy": {
const features = [SolanaSignTransactions];
if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
return {
...capabilities,
features
};
}
case "v1": return {
...capabilities,
supports_sign_and_send_transactions: true,
supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
};
}
}
}
return response;
}
async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
const domain = signInPayload.domain ?? window.location.host;
const address = authorizationResult.accounts[0].address;
const siwsMessage = createSIWSMessageBase64Url({
...signInPayload,
domain,
address: base64ToBase58(address)
});
const signedPayload = toUint8Array((await protocolRequestHandler("sign_messages", {
addresses: [address],
payloads: [siwsMessage]
})).signed_payloads[0]);
const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
return {
address,
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
signature
};
}
function createSequenceNumberVector(sequenceNumber) {
if (sequenceNumber >= 4294967296) throw new Error("Outbound sequence number overflow. The maximum sequence number is 32-bytes.");
const byteArray = /* @__PURE__ */ new ArrayBuffer(4);
new DataView(byteArray).setUint32(0, sequenceNumber, false);
return new Uint8Array(byteArray);
}
//#endregion
//#region src/encryptedMessage.ts
const INITIALIZATION_VECTOR_BYTES = 12;
async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
crypto.getRandomValues(initializationVector);
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
response.set(new Uint8Array(sequenceNumberVector), 0);
response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
return response;
}
async function decryptMessage(message, sharedSecret) {
const sequenceNumberVector = message.slice(0, 4);
const initializationVector = message.slice(4, 4 + INITIALIZATION_VECTOR_BYTES);
const ciphertext = message.slice(4 + INITIALIZATION_VECTOR_BYTES);
const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
return getUtf8Decoder().decode(plaintextBuffer);
}
function getAlgorithmParams(sequenceNumber, initializationVector) {
return {
additionalData: sequenceNumber,
iv: initializationVector,
name: "AES-GCM",
tagLength: 128
};
}
let _utf8Decoder;
function getUtf8Decoder() {
if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
return _utf8Decoder;
}
//#endregion
//#region src/generateAssociationKeypair.ts
async function generateAssociationKeypair() {
return await crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-256"
}, false, ["sign"]);
}
//#endregion
//#region src/generateECDHKeypair.ts
async function generateECDHKeypair() {
return await crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-256"
}, false, ["deriveKey", "deriveBits"]);
}
//#endregion
//#region src/arrayBufferToBase64String.ts
function arrayBufferToBase64String(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
return window.btoa(binary);
}
//#endregion
//#region src/associationPort.ts
function getRandomAssociationPort() {
return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
}
function assertAssociationPort(port) {
if (port < 49152 || port > 65535) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
return port;
}
//#endregion
//#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
"/": "_",
"+": "-",
"=": "."
})[m]);
}
//#endregion
//#region src/getAssociateAndroidIntentURL.ts
const INTENT_NAME = "solana-wallet";
function getPathParts(pathString) {
return pathString.replace(/(^\/+|\/+$)/g, "").split("/");
}
function getIntentURL(methodPathname, intentUrlBase) {
let baseUrl = null;
if (intentUrlBase) {
try {
baseUrl = new URL(intentUrlBase);
} catch {}
if (baseUrl?.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
baseUrl ||= new URL(`${INTENT_NAME}:/`);
const pathname = methodPathname.startsWith("/") ? methodPathname : [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join("/");
return new URL(pathname, baseUrl);
}
async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
const associationPort = assertAssociationPort(putativePort);
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/local", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("port", `${associationPort}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/remote", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("reflector", `${hostAuthority}`);
url.searchParams.set("id", `${fromUint8Array$1(reflectorId, true)}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
//#endregion
//#region src/jsonRpcMessage.ts
async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
const plaintext = JSON.stringify(jsonRpcMessage);
const sequenceNumber = jsonRpcMessage.id;
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
}
async function decryptJsonRpcMessage(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonRpcMessage = JSON.parse(plaintext);
if (Object.hasOwnProperty.call(jsonRpcMessage, "error")) throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
return jsonRpcMessage;
}
//#endregion
//#region src/parseHelloRsp.ts
async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey) {
const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([crypto.subtle.exportKey("raw", associationPublicKey), crypto.subtle.importKey("raw", payloadBuffer.slice(0, 65), {
name: "ECDH",
namedCurve: "P-256"
}, false, [])]);
const sharedSecret = await crypto.subtle.deriveBits({
name: "ECDH",
public: walletPublicKey
}, ecdhPrivateKey, 256);
const ecdhSecretKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
return await crypto.subtle.deriveKey({
name: "HKDF",
hash: "SHA-256",
salt: new Uint8Array(associationPublicKeyBuffer),
info: new Uint8Array()
}, ecdhSecretKey, {
name: "AES-GCM",
length: 128
}, false, ["encrypt", "decrypt"]);
}
//#endregion
//#region src/parseSessionProps.ts
async function parseSessionProps(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonProperties = JSON.parse(plaintext);
let protocolVersion = "legacy";
if (Object.hasOwnProperty.call(jsonProperties, "v")) switch (jsonProperties.v) {
case 1:
case "1":
case "v1":
protocolVersion = "v1";
break;
case "legacy":
protocolVersion = "legacy";
break;
default: throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
}
return { protocol_version: protocolVersion };
}
//#endregion
//#region src/startSession.ts
const Browser = {
Firefox: 0,
Other: 1
};
function assertUnreachable(x) {
return x;
}
function getBrowser() {
return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
}
function getDetectionPromise() {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeoutId);
window.removeEventListener("blur", handleBlur);
}
function handleBlur() {
cleanup();
resolve();
}
window.addEventListener("blur", handleBlur);
const timeoutId = setTimeout(() => {
cleanup();
reject();
}, 3e3);
});
}
let _frame = null;
function launchUrlThroughHiddenFrame(url) {
if (_frame == null) {
_frame = document.createElement("iframe");
_frame.style.display = "none";
document.body.appendChild(_frame);
}
_frame.contentWindow.location.href = url.toString();
}
async function launchAssociation(associationUrl) {
if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
else try {
const browser = getBrowser();
switch (browser) {
case Browser.Firefox:
launchUrlThroughHiddenFrame(associationUrl);
break;
case Browser.Other: {
const detectionPromise = getDetectionPromise();
window.location.assign(associationUrl);
await detectionPromise;
break;
}
default: assertUnreachable(browser);
}
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
}
}
async function startSession(associationPublicKey, associationURLBase) {
const randomAssociationPort = getRandomAssociationPort();
await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
return randomAssociationPort;
}
//#endregion
//#region src/transact.ts
const WEBSOCKET_CONNECTION_CONFIG = {
retryDelayScheduleMs: [
150,
150,
200,
500,
500,
750,
750,
1e3
],
timeoutMs: 3e4
};
const WEBSOCKET_PROTOCOL_BINARY = "com.solana.mobilewalletadapter.v1";
const WEBSOCKET_PROTOCOL_BASE64 = "com.solana.mobilewalletadapter.v1.base64";
function assertSecureContext() {
if (typeof window === "undefined" || window.isSecureContext !== true) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, "The mobile wallet adapter protocol must be used in a secure context (`https`).");
}
function assertSecureEndpointSpecificURI(walletUriBase) {
let url;
try {
url = new URL(walletUriBase);
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Invalid base URL supplied by wallet");
}
if (url.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
function getSequenceNumberFromByteArray(byteArray) {
return new DataView(byteArray).getUint32(0, false);
}
function decodeVarLong(byteArray) {
const bytes = new Uint8Array(byteArray);
const l = byteArray.byteLength;
const limit = 10;
let value = 0, offset = 0, b;
do {
if (offset >= l || offset > limit) throw new RangeError("Failed to decode varint");
b = bytes[offset++];
value |= (b & 127) << 7 * offset;
} while (b >= 128);
return {
value,
offset
};
}
function getReflectorIdFromByteArray(byteArray) {
const { value: length, offset } = decodeVarLong(byteArray);
return new Uint8Array(byteArray.slice(offset, offset + length));
}
async function transact(callback, config) {
const { wallet, close } = await startScenario(config);
try {
return await callback(await wallet);
} finally {
close();
}
}
async function startScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `ws://localhost:${await startSession(associationKeypair.publicKey, config?.baseUri)}/solana-wallet`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let state = { __type: "disconnected" };
let socket;
let sessionEstablished = false;
let handleForceClose;
return {
close: () => {
socket.close();
handleForceClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
socket.removeEventListener("open", handleOpen);
const { associationKeypair } = state;
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleMessage = async (evt) => {
const responseBuffer = await evt.data.arrayBuffer();
switch (state.__type) {
case "connecting": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while connecting");
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
if (responseBuffer.byteLength === 0) {
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
socket.send(await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret));
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
handleForceClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
let disposeSocket;
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleMessage);
};
};
attemptSocketConnection();
})
};
}
async function startRemoteScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let encoding;
let state = { __type: "disconnected" };
let socket;
let disposeSocket;
const decodeBytes = async (evt) => {
if (encoding == "base64") return toUint8Array(await evt.data).buffer;
else return await evt.data.arrayBuffer();
};
const associationUrl = await new Promise((resolve, reject) => {
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) encoding = "base64";
else encoding = "binary";
socket.removeEventListener("open", handleOpen);
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleReflectorIdMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
if (state.__type === "connecting") {
if (responseBuffer.byteLength == 0) throw new Error("Encountered unexpected message while connecting");
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
state = {
__type: "reflector_id_received",
reflectorId
};
const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
socket.removeEventListener("message", handleReflectorIdMessage);
resolve(associationUrl);
}
};
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleReflectorIdMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleReflectorIdMessage);
};
};
attemptSocketConnection();
});
let sessionEstablished = false;
let handleClose;
return {
associationUrl,
close: () => {
socket.close();
handleClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
switch (state.__type) {
case "reflector_id_received": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while awaiting reflection");
const ecdhKeypair = await generateECDHKeypair();
const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
const binaryMsg = await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
socket.addEventListener("message", handleMessage);
handleClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
})
};
}
//#endregion
exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
exports.SolanaSignTransactions = SolanaSignTransactions;
exports.startRemoteScenario = startRemoteScenario;
exports.startScenario = startScenario;
exports.transact = transact;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,280 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let react_native = require("react-native");
let _solana_codecs_strings = require("@solana/codecs-strings");
let js_base64 = require("js-base64");
let _solana_wallet_standard_util = require("@solana/wallet-standard-util");
//#region src/errors.ts
const SolanaMobileWalletAdapterErrorCode = {
ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE",
ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE",
ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL",
ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED",
ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED",
ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT",
ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND",
ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
};
var SolanaMobileWalletAdapterError = class extends Error {
data;
code;
constructor(...args) {
const [code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.name = "SolanaMobileWalletAdapterError";
}
};
const SolanaMobileWalletAdapterProtocolErrorCode = {
ERROR_AUTHORIZATION_FAILED: -1,
ERROR_INVALID_PAYLOADS: -2,
ERROR_NOT_SIGNED: -3,
ERROR_NOT_SUBMITTED: -4,
ERROR_TOO_MANY_PAYLOADS: -5,
ERROR_ATTEST_ORIGIN_ANDROID: -100
};
var SolanaMobileWalletAdapterProtocolError = class extends Error {
data;
code;
jsonRpcMessageId;
constructor(...args) {
const [jsonRpcMessageId, code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.jsonRpcMessageId = jsonRpcMessageId;
this.name = "SolanaMobileWalletAdapterProtocolError";
}
};
//#endregion
//#region src/codegenSpec/NativeSolanaMobileWalletAdapter.ts
var NativeSolanaMobileWalletAdapter_default = react_native.TurboModuleRegistry.getEnforcing("SolanaMobileWalletAdapter");
//#endregion
//#region src/base58Utils.ts
function fromUint8Array(byteArray) {
return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
}
function base64ToBase58(base64EncodedString) {
return fromUint8Array((0, js_base64.toUint8Array)(base64EncodedString));
}
//#endregion
//#region src/createSIWSMessage.ts
function createSIWSMessage(payload) {
return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
}
function createSIWSMessageBase64Url(payload) {
return (0, js_base64.encode)(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
//#endregion
//#region src/types.ts
const SolanaSignTransactions = "solana:signTransactions";
const SolanaCloneAuthorization = "solana:cloneAuthorization";
const SolanaSignInWithSolana = "solana:signInWithSolana";
//#endregion
//#region src/createMobileWalletProxy.ts
/**
* Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
*
* @param protocolVersion the protocol version in use for this session/request
* @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
* @returns a {@link MobileWallet} proxy
*/
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
return new Proxy({}, {
get(target, p) {
if (p === "then") return null;
if (target[p] == null) target[p] = async function(inputParams) {
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
const result = await protocolRequestHandler(method, params);
if (method === "authorize" && params.sign_in_payload && !result.sign_in_result) result.sign_in_result = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
return handleMobileWalletResponse(p, result, protocolVersion);
};
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
/**
* Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
* This handles backwards compatibility, based on the provided @protocolVersion.
*
* @param methodName the name of {@link MobileWallet} method that was called
* @param methodParams the parameters that were passed to the method
* @param protocolVersion the protocol version in use for this session/request
* @returns the RPC request method and params that should be sent to the wallet endpoint
*/
function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
let params = methodParams;
let method = methodName.toString().replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toLowerCase();
switch (methodName) {
case "authorize": {
const authorizeParams = params;
let { chain } = authorizeParams;
if (protocolVersion === "legacy") {
switch (chain) {
case "solana:testnet":
chain = "testnet";
break;
case "solana:devnet":
chain = "devnet";
break;
case "solana:mainnet":
chain = "mainnet-beta";
break;
default: chain = authorizeParams.cluster;
}
authorizeParams.cluster = chain;
params = authorizeParams;
} else {
switch (chain) {
case "testnet":
case "devnet":
chain = `solana:${chain}`;
break;
case "mainnet-beta":
chain = "solana:mainnet";
break;
}
authorizeParams.chain = chain;
params = authorizeParams;
}
}
case "reauthorize": {
const { auth_token, identity } = params;
if (auth_token) switch (protocolVersion) {
case "legacy":
method = "reauthorize";
params = {
auth_token,
identity
};
break;
default:
method = "authorize";
break;
}
break;
}
}
return {
method,
params
};
}
/**
* Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
*
* @param method the {@link MobileWallet} method that was called
* @param response the original response that was returned by the method call
* @param protocolVersion the protocol version in use for this session/request
* @returns the possibly modified response
*/
function handleMobileWalletResponse(method, response, protocolVersion) {
switch (method) {
case "getCapabilities": {
const capabilities = response;
switch (protocolVersion) {
case "legacy": {
const features = [SolanaSignTransactions];
if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
return {
...capabilities,
features
};
}
case "v1": return {
...capabilities,
supports_sign_and_send_transactions: true,
supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
};
}
}
}
return response;
}
async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
const domain = signInPayload.domain ?? window.location.host;
const address = authorizationResult.accounts[0].address;
const siwsMessage = createSIWSMessageBase64Url({
...signInPayload,
domain,
address: base64ToBase58(address)
});
const signedPayload = (0, js_base64.toUint8Array)((await protocolRequestHandler("sign_messages", {
addresses: [address],
payloads: [siwsMessage]
})).signed_payloads[0]);
const signedMessage = (0, js_base64.fromUint8Array)(signedPayload.slice(0, signedPayload.length - 64));
const signature = (0, js_base64.fromUint8Array)(signedPayload.slice(signedPayload.length - 64));
return {
address,
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
signature
};
}
//#endregion
//#region src/__forks__/react-native/transact.ts
react_native.AppRegistry.registerHeadlessTask("SolanaMobileWalletAdapterSessionBackgroundTask", () => {
return async () => {};
});
const LINKING_ERROR = "The package 'solana-mobile-wallet-adapter-protocol' doesn't seem to be linked. Make sure: \n\n- You rebuilt the app after installing the package\n- If you are using Lerna workspaces\n - You have added `@solana-mobile/mobile-wallet-adapter-protocol` as an explicit dependency, and\n - You have added `@solana-mobile/mobile-wallet-adapter-protocol` to the `nohoist` section of your package.json\n- You are not using Expo managed workflow\n";
const SolanaMobileWalletAdapter = react_native.Platform.OS === "android" && NativeSolanaMobileWalletAdapter_default ? NativeSolanaMobileWalletAdapter_default : new Proxy({}, { get() {
throw new Error(react_native.Platform.OS !== "android" ? "The package `solana-mobile-wallet-adapter-protocol` is only compatible with React Native Android" : LINKING_ERROR);
} });
function getErrorMessage(e) {
switch (e.code) {
case "ERROR_WALLET_NOT_FOUND": return "Found no installed wallet that supports the mobile wallet protocol.";
default: return e.message;
}
}
function handleError(e) {
if (e instanceof Error) {
const reactNativeError = e;
switch (reactNativeError.code) {
case void 0: throw e;
case "JSON_RPC_ERROR": {
const details = reactNativeError.userInfo;
throw new SolanaMobileWalletAdapterProtocolError(0, details.jsonRpcErrorCode, e.message);
}
default: throw new SolanaMobileWalletAdapterError(reactNativeError.code, getErrorMessage(reactNativeError), reactNativeError.userInfo);
}
}
throw e;
}
async function transact(callback, config) {
let didSuccessfullyConnect = false;
try {
const sessionProperties = await SolanaMobileWalletAdapter.startSession(config);
didSuccessfullyConnect = true;
return await callback(createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
try {
return SolanaMobileWalletAdapter.invoke(method, params);
} catch (e) {
return handleError(e);
}
}));
} catch (e) {
return handleError(e);
} finally {
if (didSuccessfullyConnect) await SolanaMobileWalletAdapter.endSession();
}
}
//#endregion
exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
exports.SolanaSignTransactions = SolanaSignTransactions;
exports.transact = transact;
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"commonjs"}

View File

@@ -0,0 +1,914 @@
import { getBase58Decoder } from "@solana/codecs-strings";
import { createSignInMessageText } from "@solana/wallet-standard-util";
//#region src/errors.ts
const SolanaMobileWalletAdapterErrorCode = {
ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE",
ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE",
ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL",
ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED",
ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED",
ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT",
ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND",
ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
};
var SolanaMobileWalletAdapterError = class extends Error {
data;
code;
constructor(...args) {
const [code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.name = "SolanaMobileWalletAdapterError";
}
};
const SolanaMobileWalletAdapterProtocolErrorCode = {
ERROR_AUTHORIZATION_FAILED: -1,
ERROR_INVALID_PAYLOADS: -2,
ERROR_NOT_SIGNED: -3,
ERROR_NOT_SUBMITTED: -4,
ERROR_TOO_MANY_PAYLOADS: -5,
ERROR_ATTEST_ORIGIN_ANDROID: -100
};
var SolanaMobileWalletAdapterProtocolError = class extends Error {
data;
code;
jsonRpcMessageId;
constructor(...args) {
const [jsonRpcMessageId, code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.jsonRpcMessageId = jsonRpcMessageId;
this.name = "SolanaMobileWalletAdapterProtocolError";
}
};
//#endregion
//#region src/base64Utils.ts
function encode(input) {
return window.btoa(input);
}
function fromUint8Array$1(byteArray, urlsafe) {
const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
if (urlsafe) return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
else return base64;
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/createHelloReq.ts
async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
const publicKeyBuffer = await crypto.subtle.exportKey("raw", ecdhPublicKey);
const signatureBuffer = await crypto.subtle.sign({
hash: "SHA-256",
name: "ECDSA"
}, associationKeypairPrivateKey, publicKeyBuffer);
const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
response.set(new Uint8Array(publicKeyBuffer), 0);
response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
return response;
}
//#endregion
//#region src/base58Utils.ts
function fromUint8Array(byteArray) {
return getBase58Decoder().decode(byteArray);
}
function base64ToBase58(base64EncodedString) {
return fromUint8Array(toUint8Array(base64EncodedString));
}
//#endregion
//#region src/createSIWSMessage.ts
function createSIWSMessage(payload) {
return createSignInMessageText(payload);
}
function createSIWSMessageBase64Url(payload) {
return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
//#endregion
//#region src/types.ts
const SolanaSignTransactions = "solana:signTransactions";
const SolanaCloneAuthorization = "solana:cloneAuthorization";
const SolanaSignInWithSolana = "solana:signInWithSolana";
//#endregion
//#region src/createMobileWalletProxy.ts
/**
* Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
*
* @param protocolVersion the protocol version in use for this session/request
* @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
* @returns a {@link MobileWallet} proxy
*/
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
return new Proxy({}, {
get(target, p) {
if (p === "then") return null;
if (target[p] == null) target[p] = async function(inputParams) {
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
const result = await protocolRequestHandler(method, params);
if (method === "authorize" && params.sign_in_payload && !result.sign_in_result) result.sign_in_result = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
return handleMobileWalletResponse(p, result, protocolVersion);
};
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
/**
* Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
* This handles backwards compatibility, based on the provided @protocolVersion.
*
* @param methodName the name of {@link MobileWallet} method that was called
* @param methodParams the parameters that were passed to the method
* @param protocolVersion the protocol version in use for this session/request
* @returns the RPC request method and params that should be sent to the wallet endpoint
*/
function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
let params = methodParams;
let method = methodName.toString().replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toLowerCase();
switch (methodName) {
case "authorize": {
const authorizeParams = params;
let { chain } = authorizeParams;
if (protocolVersion === "legacy") {
switch (chain) {
case "solana:testnet":
chain = "testnet";
break;
case "solana:devnet":
chain = "devnet";
break;
case "solana:mainnet":
chain = "mainnet-beta";
break;
default: chain = authorizeParams.cluster;
}
authorizeParams.cluster = chain;
params = authorizeParams;
} else {
switch (chain) {
case "testnet":
case "devnet":
chain = `solana:${chain}`;
break;
case "mainnet-beta":
chain = "solana:mainnet";
break;
}
authorizeParams.chain = chain;
params = authorizeParams;
}
}
case "reauthorize": {
const { auth_token, identity } = params;
if (auth_token) switch (protocolVersion) {
case "legacy":
method = "reauthorize";
params = {
auth_token,
identity
};
break;
default:
method = "authorize";
break;
}
break;
}
}
return {
method,
params
};
}
/**
* Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
*
* @param method the {@link MobileWallet} method that was called
* @param response the original response that was returned by the method call
* @param protocolVersion the protocol version in use for this session/request
* @returns the possibly modified response
*/
function handleMobileWalletResponse(method, response, protocolVersion) {
switch (method) {
case "getCapabilities": {
const capabilities = response;
switch (protocolVersion) {
case "legacy": {
const features = [SolanaSignTransactions];
if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
return {
...capabilities,
features
};
}
case "v1": return {
...capabilities,
supports_sign_and_send_transactions: true,
supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
};
}
}
}
return response;
}
async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
const domain = signInPayload.domain ?? window.location.host;
const address = authorizationResult.accounts[0].address;
const siwsMessage = createSIWSMessageBase64Url({
...signInPayload,
domain,
address: base64ToBase58(address)
});
const signedPayload = toUint8Array((await protocolRequestHandler("sign_messages", {
addresses: [address],
payloads: [siwsMessage]
})).signed_payloads[0]);
const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
return {
address,
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
signature
};
}
function createSequenceNumberVector(sequenceNumber) {
if (sequenceNumber >= 4294967296) throw new Error("Outbound sequence number overflow. The maximum sequence number is 32-bytes.");
const byteArray = /* @__PURE__ */ new ArrayBuffer(4);
new DataView(byteArray).setUint32(0, sequenceNumber, false);
return new Uint8Array(byteArray);
}
//#endregion
//#region src/encryptedMessage.ts
const INITIALIZATION_VECTOR_BYTES = 12;
async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
crypto.getRandomValues(initializationVector);
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
response.set(new Uint8Array(sequenceNumberVector), 0);
response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
return response;
}
async function decryptMessage(message, sharedSecret) {
const sequenceNumberVector = message.slice(0, 4);
const initializationVector = message.slice(4, 4 + INITIALIZATION_VECTOR_BYTES);
const ciphertext = message.slice(4 + INITIALIZATION_VECTOR_BYTES);
const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
return getUtf8Decoder().decode(plaintextBuffer);
}
function getAlgorithmParams(sequenceNumber, initializationVector) {
return {
additionalData: sequenceNumber,
iv: initializationVector,
name: "AES-GCM",
tagLength: 128
};
}
let _utf8Decoder;
function getUtf8Decoder() {
if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
return _utf8Decoder;
}
//#endregion
//#region src/generateAssociationKeypair.ts
async function generateAssociationKeypair() {
return await crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-256"
}, false, ["sign"]);
}
//#endregion
//#region src/generateECDHKeypair.ts
async function generateECDHKeypair() {
return await crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-256"
}, false, ["deriveKey", "deriveBits"]);
}
//#endregion
//#region src/arrayBufferToBase64String.ts
function arrayBufferToBase64String(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
return window.btoa(binary);
}
//#endregion
//#region src/associationPort.ts
function getRandomAssociationPort() {
return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
}
function assertAssociationPort(port) {
if (port < 49152 || port > 65535) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
return port;
}
//#endregion
//#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
"/": "_",
"+": "-",
"=": "."
})[m]);
}
//#endregion
//#region src/getAssociateAndroidIntentURL.ts
const INTENT_NAME = "solana-wallet";
function getPathParts(pathString) {
return pathString.replace(/(^\/+|\/+$)/g, "").split("/");
}
function getIntentURL(methodPathname, intentUrlBase) {
let baseUrl = null;
if (intentUrlBase) {
try {
baseUrl = new URL(intentUrlBase);
} catch {}
if (baseUrl?.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
baseUrl ||= new URL(`${INTENT_NAME}:/`);
const pathname = methodPathname.startsWith("/") ? methodPathname : [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join("/");
return new URL(pathname, baseUrl);
}
async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
const associationPort = assertAssociationPort(putativePort);
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/local", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("port", `${associationPort}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/remote", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("reflector", `${hostAuthority}`);
url.searchParams.set("id", `${fromUint8Array$1(reflectorId, true)}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
//#endregion
//#region src/jsonRpcMessage.ts
async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
const plaintext = JSON.stringify(jsonRpcMessage);
const sequenceNumber = jsonRpcMessage.id;
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
}
async function decryptJsonRpcMessage(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonRpcMessage = JSON.parse(plaintext);
if (Object.hasOwnProperty.call(jsonRpcMessage, "error")) throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
return jsonRpcMessage;
}
//#endregion
//#region src/parseHelloRsp.ts
async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey) {
const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([crypto.subtle.exportKey("raw", associationPublicKey), crypto.subtle.importKey("raw", payloadBuffer.slice(0, 65), {
name: "ECDH",
namedCurve: "P-256"
}, false, [])]);
const sharedSecret = await crypto.subtle.deriveBits({
name: "ECDH",
public: walletPublicKey
}, ecdhPrivateKey, 256);
const ecdhSecretKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
return await crypto.subtle.deriveKey({
name: "HKDF",
hash: "SHA-256",
salt: new Uint8Array(associationPublicKeyBuffer),
info: new Uint8Array()
}, ecdhSecretKey, {
name: "AES-GCM",
length: 128
}, false, ["encrypt", "decrypt"]);
}
//#endregion
//#region src/parseSessionProps.ts
async function parseSessionProps(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonProperties = JSON.parse(plaintext);
let protocolVersion = "legacy";
if (Object.hasOwnProperty.call(jsonProperties, "v")) switch (jsonProperties.v) {
case 1:
case "1":
case "v1":
protocolVersion = "v1";
break;
case "legacy":
protocolVersion = "legacy";
break;
default: throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
}
return { protocol_version: protocolVersion };
}
//#endregion
//#region src/startSession.ts
const Browser = {
Firefox: 0,
Other: 1
};
function assertUnreachable(x) {
return x;
}
function getBrowser() {
return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
}
function getDetectionPromise() {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeoutId);
window.removeEventListener("blur", handleBlur);
}
function handleBlur() {
cleanup();
resolve();
}
window.addEventListener("blur", handleBlur);
const timeoutId = setTimeout(() => {
cleanup();
reject();
}, 3e3);
});
}
let _frame = null;
function launchUrlThroughHiddenFrame(url) {
if (_frame == null) {
_frame = document.createElement("iframe");
_frame.style.display = "none";
document.body.appendChild(_frame);
}
_frame.contentWindow.location.href = url.toString();
}
async function launchAssociation(associationUrl) {
if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
else try {
const browser = getBrowser();
switch (browser) {
case Browser.Firefox:
launchUrlThroughHiddenFrame(associationUrl);
break;
case Browser.Other: {
const detectionPromise = getDetectionPromise();
window.location.assign(associationUrl);
await detectionPromise;
break;
}
default: assertUnreachable(browser);
}
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
}
}
async function startSession(associationPublicKey, associationURLBase) {
const randomAssociationPort = getRandomAssociationPort();
await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
return randomAssociationPort;
}
//#endregion
//#region src/transact.ts
const WEBSOCKET_CONNECTION_CONFIG = {
retryDelayScheduleMs: [
150,
150,
200,
500,
500,
750,
750,
1e3
],
timeoutMs: 3e4
};
const WEBSOCKET_PROTOCOL_BINARY = "com.solana.mobilewalletadapter.v1";
const WEBSOCKET_PROTOCOL_BASE64 = "com.solana.mobilewalletadapter.v1.base64";
function assertSecureContext() {
if (typeof window === "undefined" || window.isSecureContext !== true) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, "The mobile wallet adapter protocol must be used in a secure context (`https`).");
}
function assertSecureEndpointSpecificURI(walletUriBase) {
let url;
try {
url = new URL(walletUriBase);
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Invalid base URL supplied by wallet");
}
if (url.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
function getSequenceNumberFromByteArray(byteArray) {
return new DataView(byteArray).getUint32(0, false);
}
function decodeVarLong(byteArray) {
const bytes = new Uint8Array(byteArray);
const l = byteArray.byteLength;
const limit = 10;
let value = 0, offset = 0, b;
do {
if (offset >= l || offset > limit) throw new RangeError("Failed to decode varint");
b = bytes[offset++];
value |= (b & 127) << 7 * offset;
} while (b >= 128);
return {
value,
offset
};
}
function getReflectorIdFromByteArray(byteArray) {
const { value: length, offset } = decodeVarLong(byteArray);
return new Uint8Array(byteArray.slice(offset, offset + length));
}
async function transact(callback, config) {
const { wallet, close } = await startScenario(config);
try {
return await callback(await wallet);
} finally {
close();
}
}
async function startScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `ws://localhost:${await startSession(associationKeypair.publicKey, config?.baseUri)}/solana-wallet`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let state = { __type: "disconnected" };
let socket;
let sessionEstablished = false;
let handleForceClose;
return {
close: () => {
socket.close();
handleForceClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
socket.removeEventListener("open", handleOpen);
const { associationKeypair } = state;
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleMessage = async (evt) => {
const responseBuffer = await evt.data.arrayBuffer();
switch (state.__type) {
case "connecting": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while connecting");
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
if (responseBuffer.byteLength === 0) {
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
socket.send(await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret));
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
handleForceClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
let disposeSocket;
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleMessage);
};
};
attemptSocketConnection();
})
};
}
async function startRemoteScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let encoding;
let state = { __type: "disconnected" };
let socket;
let disposeSocket;
const decodeBytes = async (evt) => {
if (encoding == "base64") return toUint8Array(await evt.data).buffer;
else return await evt.data.arrayBuffer();
};
const associationUrl = await new Promise((resolve, reject) => {
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) encoding = "base64";
else encoding = "binary";
socket.removeEventListener("open", handleOpen);
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleReflectorIdMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
if (state.__type === "connecting") {
if (responseBuffer.byteLength == 0) throw new Error("Encountered unexpected message while connecting");
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
state = {
__type: "reflector_id_received",
reflectorId
};
const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
socket.removeEventListener("message", handleReflectorIdMessage);
resolve(associationUrl);
}
};
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleReflectorIdMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleReflectorIdMessage);
};
};
attemptSocketConnection();
});
let sessionEstablished = false;
let handleClose;
return {
associationUrl,
close: () => {
socket.close();
handleClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
switch (state.__type) {
case "reflector_id_received": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while awaiting reflection");
const ecdhKeypair = await generateECDHKeypair();
const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
const binaryMsg = await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
socket.addEventListener("message", handleMessage);
handleClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
})
};
}
//#endregion
export { SolanaCloneAuthorization, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterProtocolError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaSignInWithSolana, SolanaSignTransactions, startRemoteScenario, startScenario, transact };
//# sourceMappingURL=index.browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,914 @@
import { getBase58Decoder } from "@solana/codecs-strings";
import { createSignInMessageText } from "@solana/wallet-standard-util";
//#region src/errors.ts
const SolanaMobileWalletAdapterErrorCode = {
ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE",
ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE",
ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL",
ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED",
ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED",
ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT",
ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND",
ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
};
var SolanaMobileWalletAdapterError = class extends Error {
data;
code;
constructor(...args) {
const [code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.name = "SolanaMobileWalletAdapterError";
}
};
const SolanaMobileWalletAdapterProtocolErrorCode = {
ERROR_AUTHORIZATION_FAILED: -1,
ERROR_INVALID_PAYLOADS: -2,
ERROR_NOT_SIGNED: -3,
ERROR_NOT_SUBMITTED: -4,
ERROR_TOO_MANY_PAYLOADS: -5,
ERROR_ATTEST_ORIGIN_ANDROID: -100
};
var SolanaMobileWalletAdapterProtocolError = class extends Error {
data;
code;
jsonRpcMessageId;
constructor(...args) {
const [jsonRpcMessageId, code, message, data] = args;
super(message);
this.code = code;
this.data = data;
this.jsonRpcMessageId = jsonRpcMessageId;
this.name = "SolanaMobileWalletAdapterProtocolError";
}
};
//#endregion
//#region src/base64Utils.ts
function encode(input) {
return window.btoa(input);
}
function fromUint8Array$1(byteArray, urlsafe) {
const base64 = window.btoa(String.fromCharCode.call(null, ...byteArray));
if (urlsafe) return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
else return base64;
}
function toUint8Array(base64EncodedByteArray) {
return new Uint8Array(window.atob(base64EncodedByteArray).split("").map((c) => c.charCodeAt(0)));
}
//#endregion
//#region src/createHelloReq.ts
async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
const publicKeyBuffer = await crypto.subtle.exportKey("raw", ecdhPublicKey);
const signatureBuffer = await crypto.subtle.sign({
hash: "SHA-256",
name: "ECDSA"
}, associationKeypairPrivateKey, publicKeyBuffer);
const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
response.set(new Uint8Array(publicKeyBuffer), 0);
response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
return response;
}
//#endregion
//#region src/base58Utils.ts
function fromUint8Array(byteArray) {
return getBase58Decoder().decode(byteArray);
}
function base64ToBase58(base64EncodedString) {
return fromUint8Array(toUint8Array(base64EncodedString));
}
//#endregion
//#region src/createSIWSMessage.ts
function createSIWSMessage(payload) {
return createSignInMessageText(payload);
}
function createSIWSMessageBase64Url(payload) {
return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
//#endregion
//#region src/types.ts
const SolanaSignTransactions = "solana:signTransactions";
const SolanaCloneAuthorization = "solana:cloneAuthorization";
const SolanaSignInWithSolana = "solana:signInWithSolana";
//#endregion
//#region src/createMobileWalletProxy.ts
/**
* Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
*
* @param protocolVersion the protocol version in use for this session/request
* @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
* @returns a {@link MobileWallet} proxy
*/
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
return new Proxy({}, {
get(target, p) {
if (p === "then") return null;
if (target[p] == null) target[p] = async function(inputParams) {
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
const result = await protocolRequestHandler(method, params);
if (method === "authorize" && params.sign_in_payload && !result.sign_in_result) result.sign_in_result = await signInFallback(params.sign_in_payload, result, protocolRequestHandler);
return handleMobileWalletResponse(p, result, protocolVersion);
};
return target[p];
},
defineProperty() {
return false;
},
deleteProperty() {
return false;
}
});
}
/**
* Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
* This handles backwards compatibility, based on the provided @protocolVersion.
*
* @param methodName the name of {@link MobileWallet} method that was called
* @param methodParams the parameters that were passed to the method
* @param protocolVersion the protocol version in use for this session/request
* @returns the RPC request method and params that should be sent to the wallet endpoint
*/
function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
let params = methodParams;
let method = methodName.toString().replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).toLowerCase();
switch (methodName) {
case "authorize": {
const authorizeParams = params;
let { chain } = authorizeParams;
if (protocolVersion === "legacy") {
switch (chain) {
case "solana:testnet":
chain = "testnet";
break;
case "solana:devnet":
chain = "devnet";
break;
case "solana:mainnet":
chain = "mainnet-beta";
break;
default: chain = authorizeParams.cluster;
}
authorizeParams.cluster = chain;
params = authorizeParams;
} else {
switch (chain) {
case "testnet":
case "devnet":
chain = `solana:${chain}`;
break;
case "mainnet-beta":
chain = "solana:mainnet";
break;
}
authorizeParams.chain = chain;
params = authorizeParams;
}
}
case "reauthorize": {
const { auth_token, identity } = params;
if (auth_token) switch (protocolVersion) {
case "legacy":
method = "reauthorize";
params = {
auth_token,
identity
};
break;
default:
method = "authorize";
break;
}
break;
}
}
return {
method,
params
};
}
/**
* Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
*
* @param method the {@link MobileWallet} method that was called
* @param response the original response that was returned by the method call
* @param protocolVersion the protocol version in use for this session/request
* @returns the possibly modified response
*/
function handleMobileWalletResponse(method, response, protocolVersion) {
switch (method) {
case "getCapabilities": {
const capabilities = response;
switch (protocolVersion) {
case "legacy": {
const features = [SolanaSignTransactions];
if (capabilities.supports_clone_authorization === true) features.push(SolanaCloneAuthorization);
return {
...capabilities,
features
};
}
case "v1": return {
...capabilities,
supports_sign_and_send_transactions: true,
supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization)
};
}
}
}
return response;
}
async function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
const domain = signInPayload.domain ?? window.location.host;
const address = authorizationResult.accounts[0].address;
const siwsMessage = createSIWSMessageBase64Url({
...signInPayload,
domain,
address: base64ToBase58(address)
});
const signedPayload = toUint8Array((await protocolRequestHandler("sign_messages", {
addresses: [address],
payloads: [siwsMessage]
})).signed_payloads[0]);
const signedMessage = fromUint8Array$1(signedPayload.slice(0, signedPayload.length - 64));
const signature = fromUint8Array$1(signedPayload.slice(signedPayload.length - 64));
return {
address,
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
signature
};
}
function createSequenceNumberVector(sequenceNumber) {
if (sequenceNumber >= 4294967296) throw new Error("Outbound sequence number overflow. The maximum sequence number is 32-bytes.");
const byteArray = /* @__PURE__ */ new ArrayBuffer(4);
new DataView(byteArray).setUint32(0, sequenceNumber, false);
return new Uint8Array(byteArray);
}
//#endregion
//#region src/encryptedMessage.ts
const INITIALIZATION_VECTOR_BYTES = 12;
async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
crypto.getRandomValues(initializationVector);
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
response.set(new Uint8Array(sequenceNumberVector), 0);
response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
response.set(new Uint8Array(ciphertext), sequenceNumberVector.byteLength + initializationVector.byteLength);
return response;
}
async function decryptMessage(message, sharedSecret) {
const sequenceNumberVector = message.slice(0, 4);
const initializationVector = message.slice(4, 4 + INITIALIZATION_VECTOR_BYTES);
const ciphertext = message.slice(4 + INITIALIZATION_VECTOR_BYTES);
const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
return getUtf8Decoder().decode(plaintextBuffer);
}
function getAlgorithmParams(sequenceNumber, initializationVector) {
return {
additionalData: sequenceNumber,
iv: initializationVector,
name: "AES-GCM",
tagLength: 128
};
}
let _utf8Decoder;
function getUtf8Decoder() {
if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
return _utf8Decoder;
}
//#endregion
//#region src/generateAssociationKeypair.ts
async function generateAssociationKeypair() {
return await crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-256"
}, false, ["sign"]);
}
//#endregion
//#region src/generateECDHKeypair.ts
async function generateECDHKeypair() {
return await crypto.subtle.generateKey({
name: "ECDH",
namedCurve: "P-256"
}, false, ["deriveKey", "deriveBits"]);
}
//#endregion
//#region src/arrayBufferToBase64String.ts
function arrayBufferToBase64String(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
return window.btoa(binary);
}
//#endregion
//#region src/associationPort.ts
function getRandomAssociationPort() {
return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
}
function assertAssociationPort(port) {
if (port < 49152 || port > 65535) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE, `Association port number must be between 49152 and 65535. ${port} given.`, { port });
return port;
}
//#endregion
//#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
"/": "_",
"+": "-",
"=": "."
})[m]);
}
//#endregion
//#region src/getAssociateAndroidIntentURL.ts
const INTENT_NAME = "solana-wallet";
function getPathParts(pathString) {
return pathString.replace(/(^\/+|\/+$)/g, "").split("/");
}
function getIntentURL(methodPathname, intentUrlBase) {
let baseUrl = null;
if (intentUrlBase) {
try {
baseUrl = new URL(intentUrlBase);
} catch {}
if (baseUrl?.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
baseUrl ||= new URL(`${INTENT_NAME}:/`);
const pathname = methodPathname.startsWith("/") ? methodPathname : [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join("/");
return new URL(pathname, baseUrl);
}
async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
const associationPort = assertAssociationPort(putativePort);
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/local", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("port", `${associationPort}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
const encodedKey = arrayBufferToBase64String(await crypto.subtle.exportKey("raw", associationPublicKey));
const url = getIntentURL("v1/associate/remote", associationURLBase);
url.searchParams.set("association", getStringWithURLUnsafeCharactersReplaced(encodedKey));
url.searchParams.set("reflector", `${hostAuthority}`);
url.searchParams.set("id", `${fromUint8Array$1(reflectorId, true)}`);
protocolVersions.forEach((version) => {
url.searchParams.set("v", version);
});
return url;
}
//#endregion
//#region src/jsonRpcMessage.ts
async function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
const plaintext = JSON.stringify(jsonRpcMessage);
const sequenceNumber = jsonRpcMessage.id;
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
}
async function decryptJsonRpcMessage(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonRpcMessage = JSON.parse(plaintext);
if (Object.hasOwnProperty.call(jsonRpcMessage, "error")) throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
return jsonRpcMessage;
}
//#endregion
//#region src/parseHelloRsp.ts
async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey) {
const [associationPublicKeyBuffer, walletPublicKey] = await Promise.all([crypto.subtle.exportKey("raw", associationPublicKey), crypto.subtle.importKey("raw", payloadBuffer.slice(0, 65), {
name: "ECDH",
namedCurve: "P-256"
}, false, [])]);
const sharedSecret = await crypto.subtle.deriveBits({
name: "ECDH",
public: walletPublicKey
}, ecdhPrivateKey, 256);
const ecdhSecretKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
return await crypto.subtle.deriveKey({
name: "HKDF",
hash: "SHA-256",
salt: new Uint8Array(associationPublicKeyBuffer),
info: new Uint8Array()
}, ecdhSecretKey, {
name: "AES-GCM",
length: 128
}, false, ["encrypt", "decrypt"]);
}
//#endregion
//#region src/parseSessionProps.ts
async function parseSessionProps(message, sharedSecret) {
const plaintext = await decryptMessage(message, sharedSecret);
const jsonProperties = JSON.parse(plaintext);
let protocolVersion = "legacy";
if (Object.hasOwnProperty.call(jsonProperties, "v")) switch (jsonProperties.v) {
case 1:
case "1":
case "v1":
protocolVersion = "v1";
break;
case "legacy":
protocolVersion = "legacy";
break;
default: throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
}
return { protocol_version: protocolVersion };
}
//#endregion
//#region src/startSession.ts
const Browser = {
Firefox: 0,
Other: 1
};
function assertUnreachable(x) {
return x;
}
function getBrowser() {
return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
}
function getDetectionPromise() {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeoutId);
window.removeEventListener("blur", handleBlur);
}
function handleBlur() {
cleanup();
resolve();
}
window.addEventListener("blur", handleBlur);
const timeoutId = setTimeout(() => {
cleanup();
reject();
}, 3e3);
});
}
let _frame = null;
function launchUrlThroughHiddenFrame(url) {
if (_frame == null) {
_frame = document.createElement("iframe");
_frame.style.display = "none";
document.body.appendChild(_frame);
}
_frame.contentWindow.location.href = url.toString();
}
async function launchAssociation(associationUrl) {
if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
else try {
const browser = getBrowser();
switch (browser) {
case Browser.Firefox:
launchUrlThroughHiddenFrame(associationUrl);
break;
case Browser.Other: {
const detectionPromise = getDetectionPromise();
window.location.assign(associationUrl);
await detectionPromise;
break;
}
default: assertUnreachable(browser);
}
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
}
}
async function startSession(associationPublicKey, associationURLBase) {
const randomAssociationPort = getRandomAssociationPort();
await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
return randomAssociationPort;
}
//#endregion
//#region src/transact.ts
const WEBSOCKET_CONNECTION_CONFIG = {
retryDelayScheduleMs: [
150,
150,
200,
500,
500,
750,
750,
1e3
],
timeoutMs: 3e4
};
const WEBSOCKET_PROTOCOL_BINARY = "com.solana.mobilewalletadapter.v1";
const WEBSOCKET_PROTOCOL_BASE64 = "com.solana.mobilewalletadapter.v1.base64";
function assertSecureContext() {
if (typeof window === "undefined" || window.isSecureContext !== true) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED, "The mobile wallet adapter protocol must be used in a secure context (`https`).");
}
function assertSecureEndpointSpecificURI(walletUriBase) {
let url;
try {
url = new URL(walletUriBase);
} catch {
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Invalid base URL supplied by wallet");
}
if (url.protocol !== "https:") throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL, "Base URLs supplied by wallets must be valid `https` URLs");
}
function getSequenceNumberFromByteArray(byteArray) {
return new DataView(byteArray).getUint32(0, false);
}
function decodeVarLong(byteArray) {
const bytes = new Uint8Array(byteArray);
const l = byteArray.byteLength;
const limit = 10;
let value = 0, offset = 0, b;
do {
if (offset >= l || offset > limit) throw new RangeError("Failed to decode varint");
b = bytes[offset++];
value |= (b & 127) << 7 * offset;
} while (b >= 128);
return {
value,
offset
};
}
function getReflectorIdFromByteArray(byteArray) {
const { value: length, offset } = decodeVarLong(byteArray);
return new Uint8Array(byteArray.slice(offset, offset + length));
}
async function transact(callback, config) {
const { wallet, close } = await startScenario(config);
try {
return await callback(await wallet);
} finally {
close();
}
}
async function startScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `ws://localhost:${await startSession(associationKeypair.publicKey, config?.baseUri)}/solana-wallet`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let state = { __type: "disconnected" };
let socket;
let sessionEstablished = false;
let handleForceClose;
return {
close: () => {
socket.close();
handleForceClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
socket.removeEventListener("open", handleOpen);
const { associationKeypair } = state;
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleMessage = async (evt) => {
const responseBuffer = await evt.data.arrayBuffer();
switch (state.__type) {
case "connecting": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while connecting");
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
if (responseBuffer.byteLength === 0) {
const ecdhKeypair = await generateECDHKeypair();
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
socket.send(await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret));
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
handleForceClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
let disposeSocket;
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleMessage);
};
};
attemptSocketConnection();
})
};
}
async function startRemoteScenario(config) {
assertSecureContext();
const associationKeypair = await generateAssociationKeypair();
const websocketURL = `wss://${config?.remoteHostAuthority}/reflect`;
let connectionStartTime;
const getNextRetryDelayMs = (() => {
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
})();
let nextJsonRpcMessageId = 1;
let lastKnownInboundSequenceNumber = 0;
let encoding;
let state = { __type: "disconnected" };
let socket;
let disposeSocket;
const decodeBytes = async (evt) => {
if (encoding == "base64") return toUint8Array(await evt.data).buffer;
else return await evt.data.arrayBuffer();
};
const associationUrl = await new Promise((resolve, reject) => {
const handleOpen = async () => {
if (state.__type !== "connecting") {
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
return;
}
if (socket.protocol.includes(WEBSOCKET_PROTOCOL_BASE64)) encoding = "base64";
else encoding = "binary";
socket.removeEventListener("open", handleOpen);
};
const handleClose = (evt) => {
if (evt.wasClean) state = { __type: "disconnected" };
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
disposeSocket();
};
const handleError = async (_evt) => {
disposeSocket();
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the wallet websocket at ${websocketURL}.`));
else {
await new Promise((resolve) => {
const retryDelayMs = getNextRetryDelayMs();
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
});
attemptSocketConnection();
}
};
const handleReflectorIdMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
if (state.__type === "connecting") {
if (responseBuffer.byteLength == 0) throw new Error("Encountered unexpected message while connecting");
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
state = {
__type: "reflector_id_received",
reflectorId
};
const associationUrl = await getRemoteAssociateAndroidIntentURL(associationKeypair.publicKey, config.remoteHostAuthority, reflectorId, config?.baseUri);
socket.removeEventListener("message", handleReflectorIdMessage);
resolve(associationUrl);
}
};
let retryWaitTimeoutId;
const attemptSocketConnection = () => {
if (disposeSocket) disposeSocket();
state = {
__type: "connecting",
associationKeypair
};
if (connectionStartTime === void 0) connectionStartTime = Date.now();
socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL_BINARY, WEBSOCKET_PROTOCOL_BASE64]);
socket.addEventListener("open", handleOpen);
socket.addEventListener("close", handleClose);
socket.addEventListener("error", handleError);
socket.addEventListener("message", handleReflectorIdMessage);
disposeSocket = () => {
window.clearTimeout(retryWaitTimeoutId);
socket.removeEventListener("open", handleOpen);
socket.removeEventListener("close", handleClose);
socket.removeEventListener("error", handleError);
socket.removeEventListener("message", handleReflectorIdMessage);
};
};
attemptSocketConnection();
});
let sessionEstablished = false;
let handleClose;
return {
associationUrl,
close: () => {
socket.close();
handleClose();
},
wallet: new Promise((resolve, reject) => {
const jsonRpcResponsePromises = {};
const handleMessage = async (evt) => {
const responseBuffer = await decodeBytes(evt);
switch (state.__type) {
case "reflector_id_received": {
if (responseBuffer.byteLength !== 0) throw new Error("Encountered unexpected message while awaiting reflection");
const ecdhKeypair = await generateECDHKeypair();
const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
state = {
__type: "hello_req_sent",
associationPublicKey: associationKeypair.publicKey,
ecdhPrivateKey: ecdhKeypair.privateKey
};
break;
}
case "connected":
try {
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
delete jsonRpcResponsePromises[jsonRpcMessage.id];
responsePromise.resolve(jsonRpcMessage.result);
} catch (e) {
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
responsePromise.reject(e);
} else throw e;
}
break;
case "hello_req_sent": {
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
const sessionPropertiesBuffer = responseBuffer.slice(65);
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new Error("Encrypted message has invalid sequence number");
lastKnownInboundSequenceNumber = sequenceNumber;
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
})() : { protocol_version: "legacy" };
state = {
__type: "connected",
sharedSecret,
sessionProperties
};
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
const id = nextJsonRpcMessageId++;
const binaryMsg = await encryptJsonRpcMessage({
id,
jsonrpc: "2.0",
method,
params: params ?? {}
}, sharedSecret);
if (encoding == "base64") socket.send(fromUint8Array$1(binaryMsg));
else socket.send(binaryMsg);
return new Promise((resolve, reject) => {
jsonRpcResponsePromises[id] = {
resolve(result) {
switch (method) {
case "authorize":
case "reauthorize": {
const { wallet_uri_base } = result;
if (wallet_uri_base != null) try {
assertSecureEndpointSpecificURI(wallet_uri_base);
} catch (e) {
reject(e);
return;
}
break;
}
}
resolve(result);
},
reject
};
});
});
sessionEstablished = true;
try {
resolve(wallet);
} catch (e) {
reject(e);
}
break;
}
}
};
socket.addEventListener("message", handleMessage);
handleClose = () => {
socket.removeEventListener("message", handleMessage);
disposeSocket();
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
};
})
};
}
//#endregion
export { SolanaCloneAuthorization, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterProtocolError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaSignInWithSolana, SolanaSignTransactions, startRemoteScenario, startScenario, transact };
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"module"}

View File

@@ -0,0 +1,261 @@
import { SolanaSignInInput } from "@solana/wallet-standard-features";
import { IdentifierArray, IdentifierString, WalletAccount, WalletIcon } from "@wallet-standard/core";
//#region src/errors.d.ts
declare const SolanaMobileWalletAdapterErrorCode: {
readonly ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: "ERROR_ASSOCIATION_PORT_OUT_OF_RANGE";
readonly ERROR_REFLECTOR_ID_OUT_OF_RANGE: "ERROR_REFLECTOR_ID_OUT_OF_RANGE";
readonly ERROR_FORBIDDEN_WALLET_BASE_URL: "ERROR_FORBIDDEN_WALLET_BASE_URL";
readonly ERROR_SECURE_CONTEXT_REQUIRED: "ERROR_SECURE_CONTEXT_REQUIRED";
readonly ERROR_SESSION_CLOSED: "ERROR_SESSION_CLOSED";
readonly ERROR_SESSION_TIMEOUT: "ERROR_SESSION_TIMEOUT";
readonly ERROR_WALLET_NOT_FOUND: "ERROR_WALLET_NOT_FOUND";
readonly ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION";
readonly ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED";
readonly ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED";
readonly ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED";
};
type SolanaMobileWalletAdapterErrorCodeEnum = (typeof SolanaMobileWalletAdapterErrorCode)[keyof typeof SolanaMobileWalletAdapterErrorCode];
type ErrorDataTypeMap = {
[SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_PORT_OUT_OF_RANGE]: {
port: number;
};
[SolanaMobileWalletAdapterErrorCode.ERROR_REFLECTOR_ID_OUT_OF_RANGE]: {
id: number;
};
[SolanaMobileWalletAdapterErrorCode.ERROR_FORBIDDEN_WALLET_BASE_URL]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_SECURE_CONTEXT_REQUIRED]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED]: {
closeEvent: CloseEvent;
};
[SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_BROWSER_NOT_SUPPORTED]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_LOOPBACK_ACCESS_BLOCKED]: undefined;
[SolanaMobileWalletAdapterErrorCode.ERROR_ASSOCIATION_CANCELLED]: {
event: Event | undefined;
};
};
declare class SolanaMobileWalletAdapterError<TErrorCode extends SolanaMobileWalletAdapterErrorCodeEnum> extends Error {
data: ErrorDataTypeMap[TErrorCode] | undefined;
code: TErrorCode;
constructor(...args: ErrorDataTypeMap[TErrorCode] extends Record<string, unknown> ? [code: TErrorCode, message: string, data: ErrorDataTypeMap[TErrorCode]] : [code: TErrorCode, message: string]);
}
type JSONRPCErrorCode = number;
declare const SolanaMobileWalletAdapterProtocolErrorCode: {
readonly ERROR_AUTHORIZATION_FAILED: -1;
readonly ERROR_INVALID_PAYLOADS: -2;
readonly ERROR_NOT_SIGNED: -3;
readonly ERROR_NOT_SUBMITTED: -4;
readonly ERROR_TOO_MANY_PAYLOADS: -5;
readonly ERROR_ATTEST_ORIGIN_ANDROID: -100;
};
type SolanaMobileWalletAdapterProtocolErrorCodeEnum = (typeof SolanaMobileWalletAdapterProtocolErrorCode)[keyof typeof SolanaMobileWalletAdapterProtocolErrorCode];
type ProtocolErrorDataTypeMap = {
[SolanaMobileWalletAdapterProtocolErrorCode.ERROR_AUTHORIZATION_FAILED]: undefined;
[SolanaMobileWalletAdapterProtocolErrorCode.ERROR_INVALID_PAYLOADS]: undefined;
[SolanaMobileWalletAdapterProtocolErrorCode.ERROR_NOT_SIGNED]: undefined;
[SolanaMobileWalletAdapterProtocolErrorCode.ERROR_NOT_SUBMITTED]: undefined;
[SolanaMobileWalletAdapterProtocolErrorCode.ERROR_TOO_MANY_PAYLOADS]: undefined;
[SolanaMobileWalletAdapterProtocolErrorCode.ERROR_ATTEST_ORIGIN_ANDROID]: {
attest_origin_uri: string;
challenge: string;
context: string;
};
};
declare class SolanaMobileWalletAdapterProtocolError<TErrorCode extends SolanaMobileWalletAdapterProtocolErrorCodeEnum> extends Error {
data: ProtocolErrorDataTypeMap[TErrorCode] | undefined;
code: TErrorCode | JSONRPCErrorCode;
jsonRpcMessageId: number;
constructor(...args: ProtocolErrorDataTypeMap[TErrorCode] extends Record<string, unknown> ? [jsonRpcMessageId: number, code: TErrorCode | JSONRPCErrorCode, message: string, data: ProtocolErrorDataTypeMap[TErrorCode]] : [jsonRpcMessageId: number, code: TErrorCode | JSONRPCErrorCode, message: string]);
}
//#endregion
//#region ../../node_modules/.pnpm/@solana+web3.js@1.98.4_bufferutil@4.1.0_typescript@5.6.3_utf-8-validate@5.0.10/node_modules/@solana/web3.js/lib/index.d.ts
type TransactionVersion = 'legacy' | 0;
/**
* Versioned transaction class
*/
//#endregion
//#region src/types.d.ts
type Account = Readonly<{
address: Base64EncodedAddress;
label?: string;
icon?: WalletIcon;
chains?: IdentifierArray;
features?: IdentifierArray;
}> | WalletAccount;
/**
* Properties that wallets may present to users when an app
* asks for authorization to execute privileged methods (see
* {@link PrivilegedMethods}).
*/
type AppIdentity = Readonly<{
uri?: string;
icon?: string;
name?: string;
}>;
/**
* An ephemeral elliptic-curve keypair on the P-256 curve.
* This public key is used to create the association token.
* The private key is used during session establishment.
*/
type AssociationKeypair = CryptoKeyPair;
type ProtocolVersion = 'v1' | 'legacy';
type SessionProperties = Readonly<{
protocol_version: ProtocolVersion;
}>;
/**
* The context returned from a wallet after having authorized a given
* account for use with a given application. You can cache this and
* use it later to invoke privileged methods.
*/
type AuthorizationResult = Readonly<{
accounts: Account[];
auth_token: AuthToken;
wallet_uri_base: string;
sign_in_result?: SignInResult;
}>;
type AuthToken = string;
type Base64EncodedAddress = string;
type Base64EncodedSignature = string;
type Base64EncodedMessage = string;
type Base64EncodedSignedMessage = string;
type Base64EncodedSignedTransaction = string;
type Base64EncodedTransaction = string;
/**
* @deprecated Replaced by the 'chain' parameter, which adds multi-chain capability as per MWA 2.0 spec.
*/
type Cluster = 'devnet' | 'testnet' | 'mainnet-beta';
type Chain = IdentifierString | Cluster;
type Finality = 'confirmed' | 'finalized' | 'processed';
type WalletAssociationConfig = Readonly<{
baseUri?: string;
}>;
type RemoteWalletAssociationConfig = WalletAssociationConfig & Readonly<{
remoteHostAuthority: string;
}>;
interface AuthorizeAPI {
/**
* @deprecated Replaced by updated authorize() method, which adds MWA 2.0 spec support.
*/
authorize(params: {
cluster: Cluster;
identity: AppIdentity;
}): Promise<AuthorizationResult>;
authorize(params: {
identity: AppIdentity;
chain?: Chain;
features?: IdentifierArray;
addresses?: string[];
auth_token?: AuthToken;
sign_in_payload?: SignInPayload;
}): Promise<AuthorizationResult>;
}
interface CloneAuthorizationAPI {
cloneAuthorization(params: {
auth_token: AuthToken;
}): Promise<Readonly<{
auth_token: AuthToken;
}>>;
}
interface DeauthorizeAPI {
deauthorize(params: {
auth_token: AuthToken;
}): Promise<Readonly<Record<string, never>>>;
}
interface GetCapabilitiesAPI {
getCapabilities(): Promise<Readonly<{
max_transactions_per_request: number;
max_messages_per_request: number;
supported_transaction_versions: ReadonlyArray<TransactionVersion>;
features: IdentifierArray;
/**
* @deprecated Replaced by features array.
*/
supports_clone_authorization: boolean;
/**
* @deprecated Replaced by features array.
*/
supports_sign_and_send_transactions: boolean;
}>>;
}
interface ReauthorizeAPI {
reauthorize(params: {
auth_token: AuthToken;
identity: AppIdentity;
}): Promise<AuthorizationResult>;
}
interface SignMessagesAPI {
signMessages(params: {
addresses: Base64EncodedAddress[];
payloads: Base64EncodedMessage[];
}): Promise<Readonly<{
signed_payloads: Base64EncodedSignedMessage[];
}>>;
}
interface SignTransactionsAPI {
signTransactions(params: {
payloads: Base64EncodedTransaction[];
}): Promise<Readonly<{
signed_payloads: Base64EncodedSignedTransaction[];
}>>;
}
interface SignAndSendTransactionsAPI {
signAndSendTransactions(params: {
options?: Readonly<{
min_context_slot?: number;
commitment?: string;
skip_preflight?: boolean;
max_retries?: number;
wait_for_commitment_to_send_next_transaction?: boolean;
}>;
payloads: Base64EncodedTransaction[];
}): Promise<Readonly<{
signatures: Base64EncodedSignature[];
}>>;
}
interface MobileWallet extends AuthorizeAPI, CloneAuthorizationAPI, DeauthorizeAPI, GetCapabilitiesAPI, ReauthorizeAPI, SignMessagesAPI, SignTransactionsAPI, SignAndSendTransactionsAPI {}
interface TerminateSessionAPI {
terminateSession(): void;
}
interface RemoteMobileWallet extends MobileWallet, TerminateSessionAPI {}
declare const SolanaSignTransactions = "solana:signTransactions";
declare const SolanaCloneAuthorization = "solana:cloneAuthorization";
declare const SolanaSignInWithSolana = "solana:signInWithSolana";
type SignInPayload = Readonly<{
domain?: string;
address?: string;
statement?: string;
uri?: string;
version?: string;
chainId?: string;
nonce?: string;
issuedAt?: string;
expirationTime?: string;
notBefore?: string;
requestId?: string;
resources?: readonly string[];
}> | SolanaSignInInput;
type SignInPayloadWithRequiredFields = Partial<SignInPayload> & Required<Pick<SignInPayload, 'domain' | 'address'>>;
type SignInResult = Readonly<{
address: Base64EncodedAddress;
signed_message: Base64EncodedSignedMessage;
signature: Base64EncodedSignature;
signature_type?: string;
}>;
type Scenario = Readonly<{
wallet: Promise<MobileWallet>;
close: () => void;
}>;
type RemoteScenario = Scenario & Readonly<{
associationUrl: URL;
}>;
//#endregion
//#region src/transact.d.ts
declare function transact<TReturn>(callback: (wallet: MobileWallet) => TReturn, config?: WalletAssociationConfig): Promise<TReturn>;
declare function startScenario(config?: WalletAssociationConfig): Promise<Scenario>;
declare function startRemoteScenario(config: RemoteWalletAssociationConfig): Promise<RemoteScenario>;
//#endregion
export { Account, AppIdentity, AssociationKeypair, AuthToken, AuthorizationResult, AuthorizeAPI, Base64EncodedAddress, Base64EncodedTransaction, Chain, CloneAuthorizationAPI, type Cluster, DeauthorizeAPI, type Finality, GetCapabilitiesAPI, MobileWallet, ProtocolVersion, ReauthorizeAPI, RemoteMobileWallet, RemoteScenario, RemoteWalletAssociationConfig, Scenario, SessionProperties, SignAndSendTransactionsAPI, SignInPayload, SignInPayloadWithRequiredFields, SignInResult, SignMessagesAPI, SignTransactionsAPI, SolanaCloneAuthorization, SolanaMobileWalletAdapterError, SolanaMobileWalletAdapterErrorCode, SolanaMobileWalletAdapterProtocolError, SolanaMobileWalletAdapterProtocolErrorCode, SolanaSignInWithSolana, SolanaSignTransactions, TerminateSessionAPI, WalletAssociationConfig, startRemoteScenario, startScenario, transact };
//# sourceMappingURL=index.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,83 @@
{
"name": "@solana-mobile/mobile-wallet-adapter-protocol",
"description": "An implementation of the Solana Mobile Mobile Wallet Adapter protocol. Use this to open a session with a mobile wallet app, and to issue API calls to it.",
"version": "2.2.8",
"author": "Steven Luscher <steven.luscher@solanamobile.com>",
"repository": {
"type": "git",
"url": "git+https://github.com/solana-mobile/mobile-wallet-adapter.git"
},
"license": "Apache-2.0",
"exports": {
"edge-light": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"workerd": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"browser": {
"import": "./lib/esm/index.browser.js",
"require": "./lib/cjs/index.browser.js"
},
"node": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"react-native": "./lib/cjs/index.native.js",
"types": "./lib/types/index.d.ts"
},
"browser": {
"./lib/cjs/index.js": "./lib/cjs/index.browser.js",
"./lib/esm/index.js": "./lib/esm/index.browser.js"
},
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"react-native": "lib/cjs/index.native.js",
"types": "lib/types/index.d.ts",
"type": "module",
"files": [
"android",
"src/codegenSpec",
"!android/build",
"lib",
"LICENSE"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"dependencies": {
"@solana/codecs-strings": "^6.0.0",
"@solana/wallet-standard-features": "^1.3.0",
"@solana/wallet-standard-util": "^1.1.2",
"@wallet-standard/core": "^1.1.1",
"js-base64": "^3.7.5"
},
"devDependencies": {
"@solana/web3.js": "^1.98.4",
"@types/react-native": "^0.69.3",
"agadoo": "^3.0.0",
"cross-env": "^10.1.0",
"shx": "^0.4.0"
},
"peerDependencies": {
"react-native": ">0.74"
},
"codegenConfig": {
"name": "SolanaMobileWalletAdapter",
"type": "all",
"jsSrcsDir": "./src/codegenSpec",
"android": {
"javaPackageName": "com.solanamobile.mobilewalletadapter.reactnative"
}
},
"scripts": {
"clean": "shx rm -rf lib/*",
"build": "pnpm clean && tsdown --config ../../tsdown.config.ts",
"build:watch": "pnpm clean && tsdown --config ../../tsdown.config.ts --watch",
"check-types": "tsc -p tsconfig.json --noEmit",
"postbuild": "printf '%s' '{\"type\":\"commonjs\"}' > lib/cjs/package.json && printf '%s' '{\"type\":\"module\"}' > lib/esm/package.json"
}
}

View File

@@ -0,0 +1,15 @@
import { TurboModule, TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
startSession(config?: { baseUri?: string }): Promise<{
protocol_version: 'legacy' | 'v1';
}>;
// React Native codegen rejects the primitive `object` type in TurboModule specs.
// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
invoke(method: string, params: Object | undefined): Promise<Object>;
endSession(): Promise<boolean>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('SolanaMobileWalletAdapter') as Spec;

View File

@@ -0,0 +1,13 @@
Copyright 2022 Solana Mobile Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,118 @@
# `@solana-mobile/wallet-adapter-mobile`
This is a plugin for use with [`@solana/wallet-adapter`](https://github.com/solana-labs/wallet-adapter). It enables apps to use a native wallet app on a mobile device to sign messages and transactions, and to send transactions if the wallet offers support for sending transactions.
## Usage
Users of these libraries do not need to take any extra steps:
- `@solana/wallet-adapter-react@">=0.15.21"`
Those libraries automatically bundle the Mobile Wallet Adapter plugin, and enable it when running in a compatible mobile environment.
## Advanced usage
Developers might wish to customize the behavior of this plugin for their app. Specifying the app's name and icon, deciding which address to select in the event the wallet authorizes the app to use more than one, specifying which network cluster to communicate with, and more are made possible by creating an instance of the mobile wallet adapter like this.
```typescript
new SolanaMobileWalletAdapter({
addressSelector: createDefaultAddressSelector(),
appIdentity: {
name: 'My app',
uri: 'https://myapp.io',
icon: 'relative/path/to/icon.png',
},
authorizationResultCache: createDefaultAuthorizationResultCache(),
cluster: WalletAdapterNetwork.Devnet,
onWalletNotFound: createDefaultWalletNotFoundHandler(),
});
```
Developers who use `@solana/wallet-adapter-react@">=0.15.21"` can supply this custom instance to `WalletProvider` which will use it to override the default one.
```typescript
const wallets = useMemo(
() => [
new SolanaMobileWalletAdapter({
addressSelector: createDefaultAddressSelector(),
appIdentity: {
name: 'My app',
uri: 'https://myapp.io',
icon: 'relative/path/to/icon.png',
},
authorizationResultCache: createDefaultAuthorizationResultCache(),
cluster: WalletAdapterNetwork.Devnet,
onWalletNotFound: createDefaultWalletNotFoundHandler(),
}),
],
[],
);
return (
<ConnectionProvider endpoint={clusterApiUrl(WalletAdapterNetwork.Devnet)}>
<WalletProvider wallets={wallets}>
<MyApp />
</WalletProvider>
</ConnectionProvider>
)
```
For more information about how to use wallet adapter plugins, visit https://github.com/solana-labs/wallet-adapter
## Configuration
### App identity
The `AppIdentity` config identifies your app to a native mobile wallet. When someone connects to a wallet for the first time, the wallet may present this information in the on-screen prompt where the ask if the visitor would like to authorize your app for use with their account.
- `name` &ndash; The plain-language name of your application.
- `uri` &ndash; The uri of your application. This uri may be required to participate in [dApp identity verification](https://github.com/solana-mobile/mobile-wallet-adapter/blob/main/spec/spec.md#dapp-identity-verification) as part of the mobile wallet adapter protocol specification.
- `icon` &ndash; An icon file path, relative to the `uri`.
### Address selector
The Mobile Wallet Adapter specification allows a wallet to authorize a dApp to use one or more addresses. dApps must supply code to select a single address for use in the adapter. That code must conform to the `AddressSelector` interface.
```typescript
export interface AddressSelector {
select(addresses: Base64EncodedAddress[]): Promise<Base64EncodedAddress>;
}
```
Alternatively, you can use the included `createDefaultAddressSelector()` method to create a selector that always chooses the first address in the list.
### Authorization result cache
The first time that someone authorizes a native wallet app for use with your application, you should cache that authorization for future use. You can supply your own implementation that conforms to the `AuthorizationResultCache` interface.
```typescript
export interface AuthorizationResultCache {
clear(): Promise<void>;
get(): Promise<AuthorizationResult | undefined>;
set(authorizationResult: AuthorizationResult): Promise<void>;
}
```
Alternatively, you can use the included `createDefaultAuthorizationResultCache()` method to create a cache that reads and writes the adapter's last-obtained `AuthorizationResult` to your browser's local storage, if available.
### Cluster
Each authorization a dApp makes with a wallet is tied to a particular Solana cluster. If a dApp wants to change the cluster on which to transact, it must seek an authorization for that cluster.
### Wallet-not-found handler
When you call `connect()` but no wallet responds within a reasonable amount of time, it is presumed that no compatible wallet is installed. You must supply an `onWalletNotFound` function to handle this case.
Alternatively, you can use the included `createDefaultWalletNotFoundHandler()` method to create a function that opens the Solana Mobile ecosystem wallets webpage.
## Android Chrome Browser Issues
Chrome on Android has a policy of blocking all navigation that does not come from explicit user gestures (click, tap, swipe, keypress). As a result, MWA Intent navigation to a wallet app will be blocked if it does not come from a user gesture.
You will see an error like:
```
Navigation is blocked: solana-wallet:/v1/associate...
```
There isn't a way around this on the Android Chrome Browser, but you can write a mobile app if you need this behavior.

View File

@@ -0,0 +1,349 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let _solana_wallet_adapter_base = require("@solana/wallet-adapter-base");
let _solana_wallet_standard_features = require("@solana/wallet-standard-features");
let _solana_web3_js = require("@solana/web3.js");
let _solana_mobile_wallet_standard_mobile = require("@solana-mobile/wallet-standard-mobile");
let _wallet_standard_core = require("@wallet-standard/core");
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
//#endregion
//#region src/getIsSupported.ts
function getIsSupported() {
return typeof window !== "undefined" && window.isSecureContext && typeof document !== "undefined" && /android/i.test(navigator.userAgent);
}
//#endregion
//#region src/adapter.ts
const SolanaMobileWalletAdapterWalletName = _solana_mobile_wallet_standard_mobile.SolanaMobileWalletAdapterWalletName;
const SolanaMobileWalletAdapterRemoteWalletName = _solana_mobile_wallet_standard_mobile.SolanaMobileWalletAdapterRemoteWalletName;
const SIGNATURE_LENGTH_IN_BYTES = 64;
function isVersionedTransaction(transaction) {
return "version" in transaction;
}
function chainOrClusterToChainId(chain) {
switch (chain) {
case "mainnet-beta": return "solana:mainnet";
case "testnet": return "solana:testnet";
case "devnet": return "solana:devnet";
default: return chain;
}
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : "Unknown error";
}
var BaseSolanaMobileWalletAdapter = class extends _solana_wallet_adapter_base.BaseSignInMessageSignerWalletAdapter {
supportedTransactionVersions = new Set(["legacy", 0]);
name;
icon;
url;
#wallet;
#connecting = false;
#readyState = getIsSupported() ? _solana_wallet_adapter_base.WalletReadyState.Loadable : _solana_wallet_adapter_base.WalletReadyState.Unsupported;
#accountSelector;
#selectedAccount;
#publicKey;
#handleChangeEvent = async (properties) => {
if (properties.accounts && properties.accounts.length > 0) {
this.#declareWalletAsInstalled();
const nextSelectedAccount = await this.#accountSelector(properties.accounts);
if (nextSelectedAccount !== this.#selectedAccount) {
this.#selectedAccount = nextSelectedAccount;
this.#publicKey = void 0;
this.emit("connect", this.publicKey);
}
}
};
constructor(wallet, config) {
super();
this.#accountSelector = async (accounts) => {
const selectedBase64EncodedAddress = await config.addressSelector.select(accounts.map(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey))));
return accounts.find(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey)) === selectedBase64EncodedAddress) ?? accounts[0];
};
this.#wallet = wallet;
this.#wallet.features[_wallet_standard_core.StandardEvents].on("change", this.#handleChangeEvent);
this.name = this.#wallet.name;
this.icon = this.#wallet.icon;
this.url = this.#wallet.url;
}
get publicKey() {
if (!this.#publicKey && this.#selectedAccount) try {
this.#publicKey = new _solana_web3_js.PublicKey(this.#selectedAccount.publicKey);
} catch (e) {
throw new _solana_wallet_adapter_base.WalletPublicKeyError(e instanceof Error && e?.message || "Unknown error", e);
}
return this.#publicKey ?? null;
}
get connected() {
return this.#wallet.connected;
}
get connecting() {
return this.#connecting;
}
get readyState() {
return this.#readyState;
}
/** @deprecated Use `autoConnect()` instead. */
async autoConnect_DO_NOT_USE_OR_YOU_WILL_BE_FIRED() {
return await this.autoConnect();
}
async autoConnect() {
this.#connect(true);
}
async connect() {
this.#connect();
}
async #connect(autoConnect = false) {
if (this.connecting || this.connected) return;
return await this.#runWithGuard(async () => {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed && this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Loadable) throw new _solana_wallet_adapter_base.WalletNotReadyError();
this.#connecting = true;
try {
await this.#wallet.features[_wallet_standard_core.StandardConnect].connect({ silent: autoConnect });
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
/** @deprecated Use `connect()` or `autoConnect()` instead. */
async performAuthorization(signInPayload) {
try {
const cachedAuthorizationResult = await this.#wallet.cachedAuthorizationResult;
if (cachedAuthorizationResult) {
await this.#wallet.features[_wallet_standard_core.StandardConnect].connect({ silent: true });
return cachedAuthorizationResult;
}
if (signInPayload) await this.#wallet.features[_solana_wallet_standard_features.SolanaSignIn].signIn(signInPayload);
else await this.#wallet.features[_wallet_standard_core.StandardConnect].connect();
return await await this.#wallet.cachedAuthorizationResult;
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
}
}
async disconnect() {
return await this.#runWithGuard(async () => {
this.#connecting = false;
this.#publicKey = void 0;
this.#selectedAccount = void 0;
await this.#wallet.features[_wallet_standard_core.StandardDisconnect].disconnect();
this.emit("disconnect");
});
}
async signIn(input) {
return this.#runWithGuard(async () => {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed && this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Loadable) throw new _solana_wallet_adapter_base.WalletNotReadyError();
this.#connecting = true;
try {
const outputs = await this.#wallet.features[_solana_wallet_standard_features.SolanaSignIn].signIn({
...input,
domain: input?.domain ?? window.location.host
});
if (outputs.length > 0) return outputs[0];
else throw new Error("Sign in failed, no sign in result returned by wallet");
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
async signMessage(message) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
return (await this.#wallet.features[_solana_wallet_standard_features.SolanaSignMessage].signMessage({
account,
message
}))[0].signature;
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSignMessageError(getErrorMessage(error), error);
}
});
}
async sendTransaction(transaction, connection, options) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
function getTargetCommitment() {
let targetCommitment;
switch (connection.commitment) {
case "confirmed":
case "finalized":
case "processed":
targetCommitment = connection.commitment;
break;
default: targetCommitment = "finalized";
}
let targetPreflightCommitment;
switch (options?.preflightCommitment) {
case "confirmed":
case "finalized":
case "processed":
targetPreflightCommitment = options.preflightCommitment;
break;
case void 0:
targetPreflightCommitment = targetCommitment;
break;
default: targetPreflightCommitment = "finalized";
}
return (targetPreflightCommitment === "finalized" ? 2 : targetPreflightCommitment === "confirmed" ? 1 : 0) < (targetCommitment === "finalized" ? 2 : targetCommitment === "confirmed" ? 1 : 0) ? targetPreflightCommitment : targetCommitment;
}
if (_solana_wallet_standard_features.SolanaSignAndSendTransaction in this.#wallet.features) {
const chain = chainOrClusterToChainId(this.#wallet.currentAuthorization.chain);
const [signature] = (await this.#wallet.features[_solana_wallet_standard_features.SolanaSignAndSendTransaction].signAndSendTransaction({
account,
transaction: transaction.serialize(),
chain,
options: options ? {
skipPreflight: options.skipPreflight,
maxRetries: options.maxRetries
} : void 0
})).map((output) => {
return fromUint8Array(output.signature);
});
return signature;
} else {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
if (isVersionedTransaction(signedTransaction)) return await connection.sendTransaction(signedTransaction);
else {
const serializedTransaction = signedTransaction.serialize();
return await connection.sendRawTransaction(serializedTransaction, {
...options,
preflightCommitment: getTargetCommitment()
});
}
}
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSendTransactionError(getErrorMessage(error), error);
}
});
}
async signTransaction(transaction) {
return await this.#runWithGuard(async () => {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
return signedTransaction;
});
}
async signAllTransactions(transactions) {
return await this.#runWithGuard(async () => {
return await this.#performSignTransactions(transactions);
});
}
#declareWalletAsInstalled() {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed) this.emit("readyStateChange", this.#readyState = _solana_wallet_adapter_base.WalletReadyState.Installed);
}
#assertIsAuthorized() {
if (!this.#wallet.isAuthorized || !this.#selectedAccount) throw new _solana_wallet_adapter_base.WalletNotConnectedError();
return this.#selectedAccount;
}
async #performSignTransactions(transactions) {
const account = this.#assertIsAuthorized();
try {
if (_solana_wallet_standard_features.SolanaSignTransaction in this.#wallet.features) return this.#wallet.features[_solana_wallet_standard_features.SolanaSignTransaction].signTransaction(...transactions.map((value) => {
return {
account,
transaction: value.serialize()
};
})).then((outputs) => {
return outputs.map((output) => {
const byteArray = output.signedTransaction;
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (_solana_web3_js.VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return _solana_web3_js.Transaction.from(byteArray);
else return _solana_web3_js.VersionedTransaction.deserialize(byteArray);
});
});
else throw new Error("Connected wallet does not support signing transactions");
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSignTransactionError(getErrorMessage(error), error);
}
}
async #runWithGuard(callback) {
try {
return await callback();
} catch (e) {
this.emit("error", e instanceof _solana_wallet_adapter_base.WalletError ? e : new _solana_wallet_adapter_base.WalletError(getErrorMessage(e), e));
throw e;
}
}
};
var LocalSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain ?? config.cluster);
super(new _solana_mobile_wallet_standard_mobile.LocalSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: (0, _solana_mobile_wallet_standard_mobile.createDefaultChainSelector)(),
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var RemoteSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain);
super(new _solana_mobile_wallet_standard_mobile.RemoteSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: (0, _solana_mobile_wallet_standard_mobile.createDefaultChainSelector)(),
remoteHostAuthority: config.remoteHostAuthority,
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var SolanaMobileWalletAdapter = class extends LocalSolanaMobileWalletAdapter {};
//#endregion
//#region src/createDefaultAddressSelector.ts
function createDefaultAddressSelector() {
return { async select(addresses) {
return addresses[0];
} };
}
//#endregion
//#region src/createDefaultAuthorizationResultCache.ts
function createDefaultAuthorizationResultCache() {
return (0, _solana_mobile_wallet_standard_mobile.createDefaultAuthorizationCache)();
}
//#endregion
//#region src/createDefaultWalletNotFoundHandler.ts
async function defaultWalletNotFoundHandler(_mobileWalletAdapter) {
return (0, _solana_mobile_wallet_standard_mobile.defaultErrorModalWalletNotFoundHandler)();
}
function createDefaultWalletNotFoundHandler() {
return defaultWalletNotFoundHandler;
}
//#endregion
exports.LocalSolanaMobileWalletAdapter = LocalSolanaMobileWalletAdapter;
exports.RemoteSolanaMobileWalletAdapter = RemoteSolanaMobileWalletAdapter;
exports.SolanaMobileWalletAdapter = SolanaMobileWalletAdapter;
exports.SolanaMobileWalletAdapterRemoteWalletName = SolanaMobileWalletAdapterRemoteWalletName;
exports.SolanaMobileWalletAdapterWalletName = SolanaMobileWalletAdapterWalletName;
exports.createDefaultAddressSelector = createDefaultAddressSelector;
exports.createDefaultAuthorizationResultCache = createDefaultAuthorizationResultCache;
exports.createDefaultWalletNotFoundHandler = createDefaultWalletNotFoundHandler;
//# sourceMappingURL=index.browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,349 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let _solana_wallet_adapter_base = require("@solana/wallet-adapter-base");
let _solana_wallet_standard_features = require("@solana/wallet-standard-features");
let _solana_web3_js = require("@solana/web3.js");
let _solana_mobile_wallet_standard_mobile = require("@solana-mobile/wallet-standard-mobile");
let _wallet_standard_core = require("@wallet-standard/core");
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
//#endregion
//#region src/getIsSupported.ts
function getIsSupported() {
return typeof window !== "undefined" && window.isSecureContext && typeof document !== "undefined" && /android/i.test(navigator.userAgent);
}
//#endregion
//#region src/adapter.ts
const SolanaMobileWalletAdapterWalletName = _solana_mobile_wallet_standard_mobile.SolanaMobileWalletAdapterWalletName;
const SolanaMobileWalletAdapterRemoteWalletName = _solana_mobile_wallet_standard_mobile.SolanaMobileWalletAdapterRemoteWalletName;
const SIGNATURE_LENGTH_IN_BYTES = 64;
function isVersionedTransaction(transaction) {
return "version" in transaction;
}
function chainOrClusterToChainId(chain) {
switch (chain) {
case "mainnet-beta": return "solana:mainnet";
case "testnet": return "solana:testnet";
case "devnet": return "solana:devnet";
default: return chain;
}
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : "Unknown error";
}
var BaseSolanaMobileWalletAdapter = class extends _solana_wallet_adapter_base.BaseSignInMessageSignerWalletAdapter {
supportedTransactionVersions = new Set(["legacy", 0]);
name;
icon;
url;
#wallet;
#connecting = false;
#readyState = getIsSupported() ? _solana_wallet_adapter_base.WalletReadyState.Loadable : _solana_wallet_adapter_base.WalletReadyState.Unsupported;
#accountSelector;
#selectedAccount;
#publicKey;
#handleChangeEvent = async (properties) => {
if (properties.accounts && properties.accounts.length > 0) {
this.#declareWalletAsInstalled();
const nextSelectedAccount = await this.#accountSelector(properties.accounts);
if (nextSelectedAccount !== this.#selectedAccount) {
this.#selectedAccount = nextSelectedAccount;
this.#publicKey = void 0;
this.emit("connect", this.publicKey);
}
}
};
constructor(wallet, config) {
super();
this.#accountSelector = async (accounts) => {
const selectedBase64EncodedAddress = await config.addressSelector.select(accounts.map(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey))));
return accounts.find(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey)) === selectedBase64EncodedAddress) ?? accounts[0];
};
this.#wallet = wallet;
this.#wallet.features[_wallet_standard_core.StandardEvents].on("change", this.#handleChangeEvent);
this.name = this.#wallet.name;
this.icon = this.#wallet.icon;
this.url = this.#wallet.url;
}
get publicKey() {
if (!this.#publicKey && this.#selectedAccount) try {
this.#publicKey = new _solana_web3_js.PublicKey(this.#selectedAccount.publicKey);
} catch (e) {
throw new _solana_wallet_adapter_base.WalletPublicKeyError(e instanceof Error && e?.message || "Unknown error", e);
}
return this.#publicKey ?? null;
}
get connected() {
return this.#wallet.connected;
}
get connecting() {
return this.#connecting;
}
get readyState() {
return this.#readyState;
}
/** @deprecated Use `autoConnect()` instead. */
async autoConnect_DO_NOT_USE_OR_YOU_WILL_BE_FIRED() {
return await this.autoConnect();
}
async autoConnect() {
this.#connect(true);
}
async connect() {
this.#connect();
}
async #connect(autoConnect = false) {
if (this.connecting || this.connected) return;
return await this.#runWithGuard(async () => {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed && this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Loadable) throw new _solana_wallet_adapter_base.WalletNotReadyError();
this.#connecting = true;
try {
await this.#wallet.features[_wallet_standard_core.StandardConnect].connect({ silent: autoConnect });
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
/** @deprecated Use `connect()` or `autoConnect()` instead. */
async performAuthorization(signInPayload) {
try {
const cachedAuthorizationResult = await this.#wallet.cachedAuthorizationResult;
if (cachedAuthorizationResult) {
await this.#wallet.features[_wallet_standard_core.StandardConnect].connect({ silent: true });
return cachedAuthorizationResult;
}
if (signInPayload) await this.#wallet.features[_solana_wallet_standard_features.SolanaSignIn].signIn(signInPayload);
else await this.#wallet.features[_wallet_standard_core.StandardConnect].connect();
return await await this.#wallet.cachedAuthorizationResult;
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
}
}
async disconnect() {
return await this.#runWithGuard(async () => {
this.#connecting = false;
this.#publicKey = void 0;
this.#selectedAccount = void 0;
await this.#wallet.features[_wallet_standard_core.StandardDisconnect].disconnect();
this.emit("disconnect");
});
}
async signIn(input) {
return this.#runWithGuard(async () => {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed && this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Loadable) throw new _solana_wallet_adapter_base.WalletNotReadyError();
this.#connecting = true;
try {
const outputs = await this.#wallet.features[_solana_wallet_standard_features.SolanaSignIn].signIn({
...input,
domain: input?.domain ?? window.location.host
});
if (outputs.length > 0) return outputs[0];
else throw new Error("Sign in failed, no sign in result returned by wallet");
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
async signMessage(message) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
return (await this.#wallet.features[_solana_wallet_standard_features.SolanaSignMessage].signMessage({
account,
message
}))[0].signature;
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSignMessageError(getErrorMessage(error), error);
}
});
}
async sendTransaction(transaction, connection, options) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
function getTargetCommitment() {
let targetCommitment;
switch (connection.commitment) {
case "confirmed":
case "finalized":
case "processed":
targetCommitment = connection.commitment;
break;
default: targetCommitment = "finalized";
}
let targetPreflightCommitment;
switch (options?.preflightCommitment) {
case "confirmed":
case "finalized":
case "processed":
targetPreflightCommitment = options.preflightCommitment;
break;
case void 0:
targetPreflightCommitment = targetCommitment;
break;
default: targetPreflightCommitment = "finalized";
}
return (targetPreflightCommitment === "finalized" ? 2 : targetPreflightCommitment === "confirmed" ? 1 : 0) < (targetCommitment === "finalized" ? 2 : targetCommitment === "confirmed" ? 1 : 0) ? targetPreflightCommitment : targetCommitment;
}
if (_solana_wallet_standard_features.SolanaSignAndSendTransaction in this.#wallet.features) {
const chain = chainOrClusterToChainId(this.#wallet.currentAuthorization.chain);
const [signature] = (await this.#wallet.features[_solana_wallet_standard_features.SolanaSignAndSendTransaction].signAndSendTransaction({
account,
transaction: transaction.serialize(),
chain,
options: options ? {
skipPreflight: options.skipPreflight,
maxRetries: options.maxRetries
} : void 0
})).map((output) => {
return fromUint8Array(output.signature);
});
return signature;
} else {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
if (isVersionedTransaction(signedTransaction)) return await connection.sendTransaction(signedTransaction);
else {
const serializedTransaction = signedTransaction.serialize();
return await connection.sendRawTransaction(serializedTransaction, {
...options,
preflightCommitment: getTargetCommitment()
});
}
}
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSendTransactionError(getErrorMessage(error), error);
}
});
}
async signTransaction(transaction) {
return await this.#runWithGuard(async () => {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
return signedTransaction;
});
}
async signAllTransactions(transactions) {
return await this.#runWithGuard(async () => {
return await this.#performSignTransactions(transactions);
});
}
#declareWalletAsInstalled() {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed) this.emit("readyStateChange", this.#readyState = _solana_wallet_adapter_base.WalletReadyState.Installed);
}
#assertIsAuthorized() {
if (!this.#wallet.isAuthorized || !this.#selectedAccount) throw new _solana_wallet_adapter_base.WalletNotConnectedError();
return this.#selectedAccount;
}
async #performSignTransactions(transactions) {
const account = this.#assertIsAuthorized();
try {
if (_solana_wallet_standard_features.SolanaSignTransaction in this.#wallet.features) return this.#wallet.features[_solana_wallet_standard_features.SolanaSignTransaction].signTransaction(...transactions.map((value) => {
return {
account,
transaction: value.serialize()
};
})).then((outputs) => {
return outputs.map((output) => {
const byteArray = output.signedTransaction;
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (_solana_web3_js.VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return _solana_web3_js.Transaction.from(byteArray);
else return _solana_web3_js.VersionedTransaction.deserialize(byteArray);
});
});
else throw new Error("Connected wallet does not support signing transactions");
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSignTransactionError(getErrorMessage(error), error);
}
}
async #runWithGuard(callback) {
try {
return await callback();
} catch (e) {
this.emit("error", e instanceof _solana_wallet_adapter_base.WalletError ? e : new _solana_wallet_adapter_base.WalletError(getErrorMessage(e), e));
throw e;
}
}
};
var LocalSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain ?? config.cluster);
super(new _solana_mobile_wallet_standard_mobile.LocalSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: (0, _solana_mobile_wallet_standard_mobile.createDefaultChainSelector)(),
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var RemoteSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain);
super(new _solana_mobile_wallet_standard_mobile.RemoteSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: (0, _solana_mobile_wallet_standard_mobile.createDefaultChainSelector)(),
remoteHostAuthority: config.remoteHostAuthority,
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var SolanaMobileWalletAdapter = class extends LocalSolanaMobileWalletAdapter {};
//#endregion
//#region src/createDefaultAddressSelector.ts
function createDefaultAddressSelector() {
return { async select(addresses) {
return addresses[0];
} };
}
//#endregion
//#region src/createDefaultAuthorizationResultCache.ts
function createDefaultAuthorizationResultCache() {
return (0, _solana_mobile_wallet_standard_mobile.createDefaultAuthorizationCache)();
}
//#endregion
//#region src/createDefaultWalletNotFoundHandler.ts
async function defaultWalletNotFoundHandler(_mobileWalletAdapter) {
return (0, _solana_mobile_wallet_standard_mobile.defaultErrorModalWalletNotFoundHandler)();
}
function createDefaultWalletNotFoundHandler() {
return defaultWalletNotFoundHandler;
}
//#endregion
exports.LocalSolanaMobileWalletAdapter = LocalSolanaMobileWalletAdapter;
exports.RemoteSolanaMobileWalletAdapter = RemoteSolanaMobileWalletAdapter;
exports.SolanaMobileWalletAdapter = SolanaMobileWalletAdapter;
exports.SolanaMobileWalletAdapterRemoteWalletName = SolanaMobileWalletAdapterRemoteWalletName;
exports.SolanaMobileWalletAdapterWalletName = SolanaMobileWalletAdapterWalletName;
exports.createDefaultAddressSelector = createDefaultAddressSelector;
exports.createDefaultAuthorizationResultCache = createDefaultAuthorizationResultCache;
exports.createDefaultWalletNotFoundHandler = createDefaultWalletNotFoundHandler;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,387 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _solana_wallet_adapter_base = require("@solana/wallet-adapter-base");
let _solana_wallet_standard_features = require("@solana/wallet-standard-features");
let _solana_web3_js = require("@solana/web3.js");
let _solana_mobile_wallet_standard_mobile = require("@solana-mobile/wallet-standard-mobile");
let _wallet_standard_core = require("@wallet-standard/core");
let js_base64 = require("js-base64");
let react_native = require("react-native");
let _react_native_async_storage_async_storage = require("@react-native-async-storage/async-storage");
_react_native_async_storage_async_storage = __toESM(_react_native_async_storage_async_storage);
//#region src/__forks__/react-native/getIsSupported.ts
function getIsSupported() {
return react_native.Platform.OS === "android";
}
//#endregion
//#region src/adapter.ts
const SolanaMobileWalletAdapterWalletName = _solana_mobile_wallet_standard_mobile.SolanaMobileWalletAdapterWalletName;
const SolanaMobileWalletAdapterRemoteWalletName = _solana_mobile_wallet_standard_mobile.SolanaMobileWalletAdapterRemoteWalletName;
const SIGNATURE_LENGTH_IN_BYTES = 64;
function isVersionedTransaction(transaction) {
return "version" in transaction;
}
function chainOrClusterToChainId(chain) {
switch (chain) {
case "mainnet-beta": return "solana:mainnet";
case "testnet": return "solana:testnet";
case "devnet": return "solana:devnet";
default: return chain;
}
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : "Unknown error";
}
var BaseSolanaMobileWalletAdapter = class extends _solana_wallet_adapter_base.BaseSignInMessageSignerWalletAdapter {
supportedTransactionVersions = new Set(["legacy", 0]);
name;
icon;
url;
#wallet;
#connecting = false;
#readyState = getIsSupported() ? _solana_wallet_adapter_base.WalletReadyState.Loadable : _solana_wallet_adapter_base.WalletReadyState.Unsupported;
#accountSelector;
#selectedAccount;
#publicKey;
#handleChangeEvent = async (properties) => {
if (properties.accounts && properties.accounts.length > 0) {
this.#declareWalletAsInstalled();
const nextSelectedAccount = await this.#accountSelector(properties.accounts);
if (nextSelectedAccount !== this.#selectedAccount) {
this.#selectedAccount = nextSelectedAccount;
this.#publicKey = void 0;
this.emit("connect", this.publicKey);
}
}
};
constructor(wallet, config) {
super();
this.#accountSelector = async (accounts) => {
const selectedBase64EncodedAddress = await config.addressSelector.select(accounts.map(({ publicKey }) => (0, js_base64.fromUint8Array)(new Uint8Array(publicKey))));
return accounts.find(({ publicKey }) => (0, js_base64.fromUint8Array)(new Uint8Array(publicKey)) === selectedBase64EncodedAddress) ?? accounts[0];
};
this.#wallet = wallet;
this.#wallet.features[_wallet_standard_core.StandardEvents].on("change", this.#handleChangeEvent);
this.name = this.#wallet.name;
this.icon = this.#wallet.icon;
this.url = this.#wallet.url;
}
get publicKey() {
if (!this.#publicKey && this.#selectedAccount) try {
this.#publicKey = new _solana_web3_js.PublicKey(this.#selectedAccount.publicKey);
} catch (e) {
throw new _solana_wallet_adapter_base.WalletPublicKeyError(e instanceof Error && e?.message || "Unknown error", e);
}
return this.#publicKey ?? null;
}
get connected() {
return this.#wallet.connected;
}
get connecting() {
return this.#connecting;
}
get readyState() {
return this.#readyState;
}
/** @deprecated Use `autoConnect()` instead. */
async autoConnect_DO_NOT_USE_OR_YOU_WILL_BE_FIRED() {
return await this.autoConnect();
}
async autoConnect() {
this.#connect(true);
}
async connect() {
this.#connect();
}
async #connect(autoConnect = false) {
if (this.connecting || this.connected) return;
return await this.#runWithGuard(async () => {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed && this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Loadable) throw new _solana_wallet_adapter_base.WalletNotReadyError();
this.#connecting = true;
try {
await this.#wallet.features[_wallet_standard_core.StandardConnect].connect({ silent: autoConnect });
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
/** @deprecated Use `connect()` or `autoConnect()` instead. */
async performAuthorization(signInPayload) {
try {
const cachedAuthorizationResult = await this.#wallet.cachedAuthorizationResult;
if (cachedAuthorizationResult) {
await this.#wallet.features[_wallet_standard_core.StandardConnect].connect({ silent: true });
return cachedAuthorizationResult;
}
if (signInPayload) await this.#wallet.features[_solana_wallet_standard_features.SolanaSignIn].signIn(signInPayload);
else await this.#wallet.features[_wallet_standard_core.StandardConnect].connect();
return await await this.#wallet.cachedAuthorizationResult;
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
}
}
async disconnect() {
return await this.#runWithGuard(async () => {
this.#connecting = false;
this.#publicKey = void 0;
this.#selectedAccount = void 0;
await this.#wallet.features[_wallet_standard_core.StandardDisconnect].disconnect();
this.emit("disconnect");
});
}
async signIn(input) {
return this.#runWithGuard(async () => {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed && this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Loadable) throw new _solana_wallet_adapter_base.WalletNotReadyError();
this.#connecting = true;
try {
const outputs = await this.#wallet.features[_solana_wallet_standard_features.SolanaSignIn].signIn({
...input,
domain: input?.domain ?? window.location.host
});
if (outputs.length > 0) return outputs[0];
else throw new Error("Sign in failed, no sign in result returned by wallet");
} catch (e) {
throw new _solana_wallet_adapter_base.WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
async signMessage(message) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
return (await this.#wallet.features[_solana_wallet_standard_features.SolanaSignMessage].signMessage({
account,
message
}))[0].signature;
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSignMessageError(getErrorMessage(error), error);
}
});
}
async sendTransaction(transaction, connection, options) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
function getTargetCommitment() {
let targetCommitment;
switch (connection.commitment) {
case "confirmed":
case "finalized":
case "processed":
targetCommitment = connection.commitment;
break;
default: targetCommitment = "finalized";
}
let targetPreflightCommitment;
switch (options?.preflightCommitment) {
case "confirmed":
case "finalized":
case "processed":
targetPreflightCommitment = options.preflightCommitment;
break;
case void 0:
targetPreflightCommitment = targetCommitment;
break;
default: targetPreflightCommitment = "finalized";
}
return (targetPreflightCommitment === "finalized" ? 2 : targetPreflightCommitment === "confirmed" ? 1 : 0) < (targetCommitment === "finalized" ? 2 : targetCommitment === "confirmed" ? 1 : 0) ? targetPreflightCommitment : targetCommitment;
}
if (_solana_wallet_standard_features.SolanaSignAndSendTransaction in this.#wallet.features) {
const chain = chainOrClusterToChainId(this.#wallet.currentAuthorization.chain);
const [signature] = (await this.#wallet.features[_solana_wallet_standard_features.SolanaSignAndSendTransaction].signAndSendTransaction({
account,
transaction: transaction.serialize(),
chain,
options: options ? {
skipPreflight: options.skipPreflight,
maxRetries: options.maxRetries
} : void 0
})).map((output) => {
return (0, js_base64.fromUint8Array)(output.signature);
});
return signature;
} else {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
if (isVersionedTransaction(signedTransaction)) return await connection.sendTransaction(signedTransaction);
else {
const serializedTransaction = signedTransaction.serialize();
return await connection.sendRawTransaction(serializedTransaction, {
...options,
preflightCommitment: getTargetCommitment()
});
}
}
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSendTransactionError(getErrorMessage(error), error);
}
});
}
async signTransaction(transaction) {
return await this.#runWithGuard(async () => {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
return signedTransaction;
});
}
async signAllTransactions(transactions) {
return await this.#runWithGuard(async () => {
return await this.#performSignTransactions(transactions);
});
}
#declareWalletAsInstalled() {
if (this.#readyState !== _solana_wallet_adapter_base.WalletReadyState.Installed) this.emit("readyStateChange", this.#readyState = _solana_wallet_adapter_base.WalletReadyState.Installed);
}
#assertIsAuthorized() {
if (!this.#wallet.isAuthorized || !this.#selectedAccount) throw new _solana_wallet_adapter_base.WalletNotConnectedError();
return this.#selectedAccount;
}
async #performSignTransactions(transactions) {
const account = this.#assertIsAuthorized();
try {
if (_solana_wallet_standard_features.SolanaSignTransaction in this.#wallet.features) return this.#wallet.features[_solana_wallet_standard_features.SolanaSignTransaction].signTransaction(...transactions.map((value) => {
return {
account,
transaction: value.serialize()
};
})).then((outputs) => {
return outputs.map((output) => {
const byteArray = output.signedTransaction;
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (_solana_web3_js.VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return _solana_web3_js.Transaction.from(byteArray);
else return _solana_web3_js.VersionedTransaction.deserialize(byteArray);
});
});
else throw new Error("Connected wallet does not support signing transactions");
} catch (error) {
throw new _solana_wallet_adapter_base.WalletSignTransactionError(getErrorMessage(error), error);
}
}
async #runWithGuard(callback) {
try {
return await callback();
} catch (e) {
this.emit("error", e instanceof _solana_wallet_adapter_base.WalletError ? e : new _solana_wallet_adapter_base.WalletError(getErrorMessage(e), e));
throw e;
}
}
};
var LocalSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain ?? config.cluster);
super(new _solana_mobile_wallet_standard_mobile.LocalSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: (0, _solana_mobile_wallet_standard_mobile.createDefaultChainSelector)(),
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var RemoteSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain);
super(new _solana_mobile_wallet_standard_mobile.RemoteSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: (0, _solana_mobile_wallet_standard_mobile.createDefaultChainSelector)(),
remoteHostAuthority: config.remoteHostAuthority,
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var SolanaMobileWalletAdapter = class extends LocalSolanaMobileWalletAdapter {};
//#endregion
//#region src/createDefaultAddressSelector.ts
function createDefaultAddressSelector() {
return { async select(addresses) {
return addresses[0];
} };
}
//#endregion
//#region src/__forks__/react-native/createDefaultAuthorizationResultCache.ts
const CACHE_KEY = "SolanaMobileWalletAdapterDefaultAuthorizationCache";
function createDefaultAuthorizationResultCache() {
return {
async clear() {
try {
await _react_native_async_storage_async_storage.default.removeItem(CACHE_KEY);
} catch {}
},
async get() {
try {
return JSON.parse(await _react_native_async_storage_async_storage.default.getItem(CACHE_KEY)) || void 0;
} catch {}
},
async set(authorizationResult) {
try {
await _react_native_async_storage_async_storage.default.setItem(CACHE_KEY, JSON.stringify(authorizationResult));
} catch {}
}
};
}
//#endregion
//#region src/createDefaultWalletNotFoundHandler.ts
async function defaultWalletNotFoundHandler(_mobileWalletAdapter) {
return (0, _solana_mobile_wallet_standard_mobile.defaultErrorModalWalletNotFoundHandler)();
}
function createDefaultWalletNotFoundHandler() {
return defaultWalletNotFoundHandler;
}
//#endregion
exports.LocalSolanaMobileWalletAdapter = LocalSolanaMobileWalletAdapter;
exports.RemoteSolanaMobileWalletAdapter = RemoteSolanaMobileWalletAdapter;
exports.SolanaMobileWalletAdapter = SolanaMobileWalletAdapter;
exports.SolanaMobileWalletAdapterRemoteWalletName = SolanaMobileWalletAdapterRemoteWalletName;
exports.SolanaMobileWalletAdapterWalletName = SolanaMobileWalletAdapterWalletName;
exports.createDefaultAddressSelector = createDefaultAddressSelector;
exports.createDefaultAuthorizationResultCache = createDefaultAuthorizationResultCache;
exports.createDefaultWalletNotFoundHandler = createDefaultWalletNotFoundHandler;
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"commonjs"}

View File

@@ -0,0 +1,341 @@
import { BaseSignInMessageSignerWalletAdapter, WalletConnectionError, WalletError, WalletNotConnectedError, WalletNotReadyError, WalletPublicKeyError, WalletReadyState, WalletSendTransactionError, WalletSignMessageError, WalletSignTransactionError } from "@solana/wallet-adapter-base";
import { SolanaSignAndSendTransaction, SolanaSignIn, SolanaSignMessage, SolanaSignTransaction } from "@solana/wallet-standard-features";
import { PublicKey, Transaction, VersionedMessage, VersionedTransaction } from "@solana/web3.js";
import { LocalSolanaMobileWalletAdapterWallet, RemoteSolanaMobileWalletAdapterWallet, SolanaMobileWalletAdapterRemoteWalletName as SolanaMobileWalletAdapterRemoteWalletName$1, SolanaMobileWalletAdapterWalletName as SolanaMobileWalletAdapterWalletName$1, createDefaultAuthorizationCache, createDefaultChainSelector, defaultErrorModalWalletNotFoundHandler } from "@solana-mobile/wallet-standard-mobile";
import { StandardConnect, StandardDisconnect, StandardEvents } from "@wallet-standard/core";
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
//#endregion
//#region src/getIsSupported.ts
function getIsSupported() {
return typeof window !== "undefined" && window.isSecureContext && typeof document !== "undefined" && /android/i.test(navigator.userAgent);
}
//#endregion
//#region src/adapter.ts
const SolanaMobileWalletAdapterWalletName = SolanaMobileWalletAdapterWalletName$1;
const SolanaMobileWalletAdapterRemoteWalletName = SolanaMobileWalletAdapterRemoteWalletName$1;
const SIGNATURE_LENGTH_IN_BYTES = 64;
function isVersionedTransaction(transaction) {
return "version" in transaction;
}
function chainOrClusterToChainId(chain) {
switch (chain) {
case "mainnet-beta": return "solana:mainnet";
case "testnet": return "solana:testnet";
case "devnet": return "solana:devnet";
default: return chain;
}
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : "Unknown error";
}
var BaseSolanaMobileWalletAdapter = class extends BaseSignInMessageSignerWalletAdapter {
supportedTransactionVersions = new Set(["legacy", 0]);
name;
icon;
url;
#wallet;
#connecting = false;
#readyState = getIsSupported() ? WalletReadyState.Loadable : WalletReadyState.Unsupported;
#accountSelector;
#selectedAccount;
#publicKey;
#handleChangeEvent = async (properties) => {
if (properties.accounts && properties.accounts.length > 0) {
this.#declareWalletAsInstalled();
const nextSelectedAccount = await this.#accountSelector(properties.accounts);
if (nextSelectedAccount !== this.#selectedAccount) {
this.#selectedAccount = nextSelectedAccount;
this.#publicKey = void 0;
this.emit("connect", this.publicKey);
}
}
};
constructor(wallet, config) {
super();
this.#accountSelector = async (accounts) => {
const selectedBase64EncodedAddress = await config.addressSelector.select(accounts.map(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey))));
return accounts.find(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey)) === selectedBase64EncodedAddress) ?? accounts[0];
};
this.#wallet = wallet;
this.#wallet.features[StandardEvents].on("change", this.#handleChangeEvent);
this.name = this.#wallet.name;
this.icon = this.#wallet.icon;
this.url = this.#wallet.url;
}
get publicKey() {
if (!this.#publicKey && this.#selectedAccount) try {
this.#publicKey = new PublicKey(this.#selectedAccount.publicKey);
} catch (e) {
throw new WalletPublicKeyError(e instanceof Error && e?.message || "Unknown error", e);
}
return this.#publicKey ?? null;
}
get connected() {
return this.#wallet.connected;
}
get connecting() {
return this.#connecting;
}
get readyState() {
return this.#readyState;
}
/** @deprecated Use `autoConnect()` instead. */
async autoConnect_DO_NOT_USE_OR_YOU_WILL_BE_FIRED() {
return await this.autoConnect();
}
async autoConnect() {
this.#connect(true);
}
async connect() {
this.#connect();
}
async #connect(autoConnect = false) {
if (this.connecting || this.connected) return;
return await this.#runWithGuard(async () => {
if (this.#readyState !== WalletReadyState.Installed && this.#readyState !== WalletReadyState.Loadable) throw new WalletNotReadyError();
this.#connecting = true;
try {
await this.#wallet.features[StandardConnect].connect({ silent: autoConnect });
} catch (e) {
throw new WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
/** @deprecated Use `connect()` or `autoConnect()` instead. */
async performAuthorization(signInPayload) {
try {
const cachedAuthorizationResult = await this.#wallet.cachedAuthorizationResult;
if (cachedAuthorizationResult) {
await this.#wallet.features[StandardConnect].connect({ silent: true });
return cachedAuthorizationResult;
}
if (signInPayload) await this.#wallet.features[SolanaSignIn].signIn(signInPayload);
else await this.#wallet.features[StandardConnect].connect();
return await await this.#wallet.cachedAuthorizationResult;
} catch (e) {
throw new WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
}
}
async disconnect() {
return await this.#runWithGuard(async () => {
this.#connecting = false;
this.#publicKey = void 0;
this.#selectedAccount = void 0;
await this.#wallet.features[StandardDisconnect].disconnect();
this.emit("disconnect");
});
}
async signIn(input) {
return this.#runWithGuard(async () => {
if (this.#readyState !== WalletReadyState.Installed && this.#readyState !== WalletReadyState.Loadable) throw new WalletNotReadyError();
this.#connecting = true;
try {
const outputs = await this.#wallet.features[SolanaSignIn].signIn({
...input,
domain: input?.domain ?? window.location.host
});
if (outputs.length > 0) return outputs[0];
else throw new Error("Sign in failed, no sign in result returned by wallet");
} catch (e) {
throw new WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
async signMessage(message) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
return (await this.#wallet.features[SolanaSignMessage].signMessage({
account,
message
}))[0].signature;
} catch (error) {
throw new WalletSignMessageError(getErrorMessage(error), error);
}
});
}
async sendTransaction(transaction, connection, options) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
function getTargetCommitment() {
let targetCommitment;
switch (connection.commitment) {
case "confirmed":
case "finalized":
case "processed":
targetCommitment = connection.commitment;
break;
default: targetCommitment = "finalized";
}
let targetPreflightCommitment;
switch (options?.preflightCommitment) {
case "confirmed":
case "finalized":
case "processed":
targetPreflightCommitment = options.preflightCommitment;
break;
case void 0:
targetPreflightCommitment = targetCommitment;
break;
default: targetPreflightCommitment = "finalized";
}
return (targetPreflightCommitment === "finalized" ? 2 : targetPreflightCommitment === "confirmed" ? 1 : 0) < (targetCommitment === "finalized" ? 2 : targetCommitment === "confirmed" ? 1 : 0) ? targetPreflightCommitment : targetCommitment;
}
if (SolanaSignAndSendTransaction in this.#wallet.features) {
const chain = chainOrClusterToChainId(this.#wallet.currentAuthorization.chain);
const [signature] = (await this.#wallet.features[SolanaSignAndSendTransaction].signAndSendTransaction({
account,
transaction: transaction.serialize(),
chain,
options: options ? {
skipPreflight: options.skipPreflight,
maxRetries: options.maxRetries
} : void 0
})).map((output) => {
return fromUint8Array(output.signature);
});
return signature;
} else {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
if (isVersionedTransaction(signedTransaction)) return await connection.sendTransaction(signedTransaction);
else {
const serializedTransaction = signedTransaction.serialize();
return await connection.sendRawTransaction(serializedTransaction, {
...options,
preflightCommitment: getTargetCommitment()
});
}
}
} catch (error) {
throw new WalletSendTransactionError(getErrorMessage(error), error);
}
});
}
async signTransaction(transaction) {
return await this.#runWithGuard(async () => {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
return signedTransaction;
});
}
async signAllTransactions(transactions) {
return await this.#runWithGuard(async () => {
return await this.#performSignTransactions(transactions);
});
}
#declareWalletAsInstalled() {
if (this.#readyState !== WalletReadyState.Installed) this.emit("readyStateChange", this.#readyState = WalletReadyState.Installed);
}
#assertIsAuthorized() {
if (!this.#wallet.isAuthorized || !this.#selectedAccount) throw new WalletNotConnectedError();
return this.#selectedAccount;
}
async #performSignTransactions(transactions) {
const account = this.#assertIsAuthorized();
try {
if (SolanaSignTransaction in this.#wallet.features) return this.#wallet.features[SolanaSignTransaction].signTransaction(...transactions.map((value) => {
return {
account,
transaction: value.serialize()
};
})).then((outputs) => {
return outputs.map((output) => {
const byteArray = output.signedTransaction;
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return Transaction.from(byteArray);
else return VersionedTransaction.deserialize(byteArray);
});
});
else throw new Error("Connected wallet does not support signing transactions");
} catch (error) {
throw new WalletSignTransactionError(getErrorMessage(error), error);
}
}
async #runWithGuard(callback) {
try {
return await callback();
} catch (e) {
this.emit("error", e instanceof WalletError ? e : new WalletError(getErrorMessage(e), e));
throw e;
}
}
};
var LocalSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain ?? config.cluster);
super(new LocalSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: createDefaultChainSelector(),
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var RemoteSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain);
super(new RemoteSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: createDefaultChainSelector(),
remoteHostAuthority: config.remoteHostAuthority,
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var SolanaMobileWalletAdapter = class extends LocalSolanaMobileWalletAdapter {};
//#endregion
//#region src/createDefaultAddressSelector.ts
function createDefaultAddressSelector() {
return { async select(addresses) {
return addresses[0];
} };
}
//#endregion
//#region src/createDefaultAuthorizationResultCache.ts
function createDefaultAuthorizationResultCache() {
return createDefaultAuthorizationCache();
}
//#endregion
//#region src/createDefaultWalletNotFoundHandler.ts
async function defaultWalletNotFoundHandler(_mobileWalletAdapter) {
return defaultErrorModalWalletNotFoundHandler();
}
function createDefaultWalletNotFoundHandler() {
return defaultWalletNotFoundHandler;
}
//#endregion
export { LocalSolanaMobileWalletAdapter, RemoteSolanaMobileWalletAdapter, SolanaMobileWalletAdapter, SolanaMobileWalletAdapterRemoteWalletName, SolanaMobileWalletAdapterWalletName, createDefaultAddressSelector, createDefaultAuthorizationResultCache, createDefaultWalletNotFoundHandler };
//# sourceMappingURL=index.browser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,341 @@
import { BaseSignInMessageSignerWalletAdapter, WalletConnectionError, WalletError, WalletNotConnectedError, WalletNotReadyError, WalletPublicKeyError, WalletReadyState, WalletSendTransactionError, WalletSignMessageError, WalletSignTransactionError } from "@solana/wallet-adapter-base";
import { SolanaSignAndSendTransaction, SolanaSignIn, SolanaSignMessage, SolanaSignTransaction } from "@solana/wallet-standard-features";
import { PublicKey, Transaction, VersionedMessage, VersionedTransaction } from "@solana/web3.js";
import { LocalSolanaMobileWalletAdapterWallet, RemoteSolanaMobileWalletAdapterWallet, SolanaMobileWalletAdapterRemoteWalletName as SolanaMobileWalletAdapterRemoteWalletName$1, SolanaMobileWalletAdapterWalletName as SolanaMobileWalletAdapterWalletName$1, createDefaultAuthorizationCache, createDefaultChainSelector, defaultErrorModalWalletNotFoundHandler } from "@solana-mobile/wallet-standard-mobile";
import { StandardConnect, StandardDisconnect, StandardEvents } from "@wallet-standard/core";
//#region src/base64Utils.ts
function fromUint8Array(byteArray) {
return window.btoa(String.fromCharCode.call(null, ...byteArray));
}
//#endregion
//#region src/getIsSupported.ts
function getIsSupported() {
return typeof window !== "undefined" && window.isSecureContext && typeof document !== "undefined" && /android/i.test(navigator.userAgent);
}
//#endregion
//#region src/adapter.ts
const SolanaMobileWalletAdapterWalletName = SolanaMobileWalletAdapterWalletName$1;
const SolanaMobileWalletAdapterRemoteWalletName = SolanaMobileWalletAdapterRemoteWalletName$1;
const SIGNATURE_LENGTH_IN_BYTES = 64;
function isVersionedTransaction(transaction) {
return "version" in transaction;
}
function chainOrClusterToChainId(chain) {
switch (chain) {
case "mainnet-beta": return "solana:mainnet";
case "testnet": return "solana:testnet";
case "devnet": return "solana:devnet";
default: return chain;
}
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : "Unknown error";
}
var BaseSolanaMobileWalletAdapter = class extends BaseSignInMessageSignerWalletAdapter {
supportedTransactionVersions = new Set(["legacy", 0]);
name;
icon;
url;
#wallet;
#connecting = false;
#readyState = getIsSupported() ? WalletReadyState.Loadable : WalletReadyState.Unsupported;
#accountSelector;
#selectedAccount;
#publicKey;
#handleChangeEvent = async (properties) => {
if (properties.accounts && properties.accounts.length > 0) {
this.#declareWalletAsInstalled();
const nextSelectedAccount = await this.#accountSelector(properties.accounts);
if (nextSelectedAccount !== this.#selectedAccount) {
this.#selectedAccount = nextSelectedAccount;
this.#publicKey = void 0;
this.emit("connect", this.publicKey);
}
}
};
constructor(wallet, config) {
super();
this.#accountSelector = async (accounts) => {
const selectedBase64EncodedAddress = await config.addressSelector.select(accounts.map(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey))));
return accounts.find(({ publicKey }) => fromUint8Array(new Uint8Array(publicKey)) === selectedBase64EncodedAddress) ?? accounts[0];
};
this.#wallet = wallet;
this.#wallet.features[StandardEvents].on("change", this.#handleChangeEvent);
this.name = this.#wallet.name;
this.icon = this.#wallet.icon;
this.url = this.#wallet.url;
}
get publicKey() {
if (!this.#publicKey && this.#selectedAccount) try {
this.#publicKey = new PublicKey(this.#selectedAccount.publicKey);
} catch (e) {
throw new WalletPublicKeyError(e instanceof Error && e?.message || "Unknown error", e);
}
return this.#publicKey ?? null;
}
get connected() {
return this.#wallet.connected;
}
get connecting() {
return this.#connecting;
}
get readyState() {
return this.#readyState;
}
/** @deprecated Use `autoConnect()` instead. */
async autoConnect_DO_NOT_USE_OR_YOU_WILL_BE_FIRED() {
return await this.autoConnect();
}
async autoConnect() {
this.#connect(true);
}
async connect() {
this.#connect();
}
async #connect(autoConnect = false) {
if (this.connecting || this.connected) return;
return await this.#runWithGuard(async () => {
if (this.#readyState !== WalletReadyState.Installed && this.#readyState !== WalletReadyState.Loadable) throw new WalletNotReadyError();
this.#connecting = true;
try {
await this.#wallet.features[StandardConnect].connect({ silent: autoConnect });
} catch (e) {
throw new WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
/** @deprecated Use `connect()` or `autoConnect()` instead. */
async performAuthorization(signInPayload) {
try {
const cachedAuthorizationResult = await this.#wallet.cachedAuthorizationResult;
if (cachedAuthorizationResult) {
await this.#wallet.features[StandardConnect].connect({ silent: true });
return cachedAuthorizationResult;
}
if (signInPayload) await this.#wallet.features[SolanaSignIn].signIn(signInPayload);
else await this.#wallet.features[StandardConnect].connect();
return await await this.#wallet.cachedAuthorizationResult;
} catch (e) {
throw new WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
}
}
async disconnect() {
return await this.#runWithGuard(async () => {
this.#connecting = false;
this.#publicKey = void 0;
this.#selectedAccount = void 0;
await this.#wallet.features[StandardDisconnect].disconnect();
this.emit("disconnect");
});
}
async signIn(input) {
return this.#runWithGuard(async () => {
if (this.#readyState !== WalletReadyState.Installed && this.#readyState !== WalletReadyState.Loadable) throw new WalletNotReadyError();
this.#connecting = true;
try {
const outputs = await this.#wallet.features[SolanaSignIn].signIn({
...input,
domain: input?.domain ?? window.location.host
});
if (outputs.length > 0) return outputs[0];
else throw new Error("Sign in failed, no sign in result returned by wallet");
} catch (e) {
throw new WalletConnectionError(e instanceof Error && e.message || "Unknown error", e);
} finally {
this.#connecting = false;
}
});
}
async signMessage(message) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
return (await this.#wallet.features[SolanaSignMessage].signMessage({
account,
message
}))[0].signature;
} catch (error) {
throw new WalletSignMessageError(getErrorMessage(error), error);
}
});
}
async sendTransaction(transaction, connection, options) {
return await this.#runWithGuard(async () => {
const account = this.#assertIsAuthorized();
try {
function getTargetCommitment() {
let targetCommitment;
switch (connection.commitment) {
case "confirmed":
case "finalized":
case "processed":
targetCommitment = connection.commitment;
break;
default: targetCommitment = "finalized";
}
let targetPreflightCommitment;
switch (options?.preflightCommitment) {
case "confirmed":
case "finalized":
case "processed":
targetPreflightCommitment = options.preflightCommitment;
break;
case void 0:
targetPreflightCommitment = targetCommitment;
break;
default: targetPreflightCommitment = "finalized";
}
return (targetPreflightCommitment === "finalized" ? 2 : targetPreflightCommitment === "confirmed" ? 1 : 0) < (targetCommitment === "finalized" ? 2 : targetCommitment === "confirmed" ? 1 : 0) ? targetPreflightCommitment : targetCommitment;
}
if (SolanaSignAndSendTransaction in this.#wallet.features) {
const chain = chainOrClusterToChainId(this.#wallet.currentAuthorization.chain);
const [signature] = (await this.#wallet.features[SolanaSignAndSendTransaction].signAndSendTransaction({
account,
transaction: transaction.serialize(),
chain,
options: options ? {
skipPreflight: options.skipPreflight,
maxRetries: options.maxRetries
} : void 0
})).map((output) => {
return fromUint8Array(output.signature);
});
return signature;
} else {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
if (isVersionedTransaction(signedTransaction)) return await connection.sendTransaction(signedTransaction);
else {
const serializedTransaction = signedTransaction.serialize();
return await connection.sendRawTransaction(serializedTransaction, {
...options,
preflightCommitment: getTargetCommitment()
});
}
}
} catch (error) {
throw new WalletSendTransactionError(getErrorMessage(error), error);
}
});
}
async signTransaction(transaction) {
return await this.#runWithGuard(async () => {
const [signedTransaction] = await this.#performSignTransactions([transaction]);
return signedTransaction;
});
}
async signAllTransactions(transactions) {
return await this.#runWithGuard(async () => {
return await this.#performSignTransactions(transactions);
});
}
#declareWalletAsInstalled() {
if (this.#readyState !== WalletReadyState.Installed) this.emit("readyStateChange", this.#readyState = WalletReadyState.Installed);
}
#assertIsAuthorized() {
if (!this.#wallet.isAuthorized || !this.#selectedAccount) throw new WalletNotConnectedError();
return this.#selectedAccount;
}
async #performSignTransactions(transactions) {
const account = this.#assertIsAuthorized();
try {
if (SolanaSignTransaction in this.#wallet.features) return this.#wallet.features[SolanaSignTransaction].signTransaction(...transactions.map((value) => {
return {
account,
transaction: value.serialize()
};
})).then((outputs) => {
return outputs.map((output) => {
const byteArray = output.signedTransaction;
const messageOffset = byteArray[0] * SIGNATURE_LENGTH_IN_BYTES + 1;
if (VersionedMessage.deserializeMessageVersion(byteArray.slice(messageOffset, byteArray.length)) === "legacy") return Transaction.from(byteArray);
else return VersionedTransaction.deserialize(byteArray);
});
});
else throw new Error("Connected wallet does not support signing transactions");
} catch (error) {
throw new WalletSignTransactionError(getErrorMessage(error), error);
}
}
async #runWithGuard(callback) {
try {
return await callback();
} catch (e) {
this.emit("error", e instanceof WalletError ? e : new WalletError(getErrorMessage(e), e));
throw e;
}
}
};
var LocalSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain ?? config.cluster);
super(new LocalSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: createDefaultChainSelector(),
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var RemoteSolanaMobileWalletAdapter = class extends BaseSolanaMobileWalletAdapter {
constructor(config) {
const chain = chainOrClusterToChainId(config.chain);
super(new RemoteSolanaMobileWalletAdapterWallet({
appIdentity: config.appIdentity,
authorizationCache: {
set: config.authorizationResultCache.set,
get: async () => {
return await config.authorizationResultCache.get();
},
clear: config.authorizationResultCache.clear
},
chains: [chain],
chainSelector: createDefaultChainSelector(),
remoteHostAuthority: config.remoteHostAuthority,
onWalletNotFound: async () => {
config.onWalletNotFound(this);
}
}), {
addressSelector: config.addressSelector,
chain
});
}
};
var SolanaMobileWalletAdapter = class extends LocalSolanaMobileWalletAdapter {};
//#endregion
//#region src/createDefaultAddressSelector.ts
function createDefaultAddressSelector() {
return { async select(addresses) {
return addresses[0];
} };
}
//#endregion
//#region src/createDefaultAuthorizationResultCache.ts
function createDefaultAuthorizationResultCache() {
return createDefaultAuthorizationCache();
}
//#endregion
//#region src/createDefaultWalletNotFoundHandler.ts
async function defaultWalletNotFoundHandler(_mobileWalletAdapter) {
return defaultErrorModalWalletNotFoundHandler();
}
function createDefaultWalletNotFoundHandler() {
return defaultWalletNotFoundHandler;
}
//#endregion
export { LocalSolanaMobileWalletAdapter, RemoteSolanaMobileWalletAdapter, SolanaMobileWalletAdapter, SolanaMobileWalletAdapterRemoteWalletName, SolanaMobileWalletAdapterWalletName, createDefaultAddressSelector, createDefaultAuthorizationResultCache, createDefaultWalletNotFoundHandler };
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"module"}

View File

@@ -0,0 +1,86 @@
import { BaseSignInMessageSignerWalletAdapter, WalletName, WalletReadyState } from "@solana/wallet-adapter-base";
import { SolanaSignInInput, SolanaSignInOutput } from "@solana/wallet-standard-features";
import { Connection, PublicKey, SendOptions, Transaction, TransactionSignature, TransactionVersion, VersionedTransaction } from "@solana/web3.js";
import { AppIdentity, AuthorizationResult, Base64EncodedAddress, Chain, Cluster, SignInPayload } from "@solana-mobile/mobile-wallet-adapter-protocol";
import { Authorization, LocalSolanaMobileWalletAdapterWallet, RemoteSolanaMobileWalletAdapterWallet } from "@solana-mobile/wallet-standard-mobile";
//#region src/adapter.d.ts
interface AuthorizationResultCache {
clear(): Promise<void>;
get(): Promise<AuthorizationResult | Authorization | undefined>;
set(authorizationResult: AuthorizationResult | Authorization): Promise<void>;
}
interface AddressSelector {
select(addresses: Base64EncodedAddress[]): Promise<Base64EncodedAddress>;
}
declare const SolanaMobileWalletAdapterWalletName: WalletName;
declare const SolanaMobileWalletAdapterRemoteWalletName: WalletName;
declare abstract class BaseSolanaMobileWalletAdapter extends BaseSignInMessageSignerWalletAdapter {
#private;
readonly supportedTransactionVersions: Set<TransactionVersion>;
name: WalletName;
icon: `data:image/svg+xml;base64,${string}` | `data:image/webp;base64,${string}` | `data:image/png;base64,${string}` | `data:image/gif;base64,${string}`;
url: string;
protected constructor(wallet: LocalSolanaMobileWalletAdapterWallet | RemoteSolanaMobileWalletAdapterWallet, config: {
addressSelector: AddressSelector;
chain: Chain;
});
get publicKey(): PublicKey | null;
get connected(): boolean;
get connecting(): boolean;
get readyState(): WalletReadyState;
/** @deprecated Use `autoConnect()` instead. */
autoConnect_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(): Promise<void>;
autoConnect(): Promise<void>;
connect(): Promise<void>;
/** @deprecated Use `connect()` or `autoConnect()` instead. */
performAuthorization(signInPayload?: SignInPayload): Promise<AuthorizationResult>;
disconnect(): Promise<void>;
signIn(input?: SolanaSignInInput): Promise<SolanaSignInOutput>;
signMessage(message: Uint8Array): Promise<Uint8Array>;
sendTransaction<T extends Transaction | VersionedTransaction>(transaction: T, connection: Connection, options?: SendOptions): Promise<TransactionSignature>;
signTransaction<T extends Transaction | VersionedTransaction>(transaction: T): Promise<T>;
signAllTransactions<T extends Transaction | VersionedTransaction>(transactions: T[]): Promise<T[]>;
}
declare class LocalSolanaMobileWalletAdapter extends BaseSolanaMobileWalletAdapter {
/**
* @deprecated @param cluster config paramter is deprecated, use @param chain instead
*/
constructor(config: {
addressSelector: AddressSelector;
appIdentity: AppIdentity;
authorizationResultCache: AuthorizationResultCache;
cluster: Cluster;
onWalletNotFound: (mobileWalletAdapter: LocalSolanaMobileWalletAdapter) => Promise<void>;
});
constructor(config: {
addressSelector: AddressSelector;
appIdentity: AppIdentity;
authorizationResultCache: AuthorizationResultCache;
chain: Chain;
onWalletNotFound: (mobileWalletAdapter: LocalSolanaMobileWalletAdapter) => Promise<void>;
});
}
declare class RemoteSolanaMobileWalletAdapter extends BaseSolanaMobileWalletAdapter {
constructor(config: {
addressSelector: AddressSelector;
appIdentity: AppIdentity;
authorizationResultCache: AuthorizationResultCache;
chain: Chain;
remoteHostAuthority: string;
onWalletNotFound: (mobileWalletAdapter: RemoteSolanaMobileWalletAdapter) => Promise<void>;
});
}
declare class SolanaMobileWalletAdapter extends LocalSolanaMobileWalletAdapter {}
//#endregion
//#region src/createDefaultAddressSelector.d.ts
declare function createDefaultAddressSelector(): AddressSelector;
//#endregion
//#region src/createDefaultAuthorizationResultCache.d.ts
declare function createDefaultAuthorizationResultCache(): AuthorizationResultCache;
//#endregion
//#region src/createDefaultWalletNotFoundHandler.d.ts
declare function createDefaultWalletNotFoundHandler(): (mobileWalletAdapter: SolanaMobileWalletAdapter) => Promise<void>;
//#endregion
export { AddressSelector, AuthorizationResultCache, LocalSolanaMobileWalletAdapter, RemoteSolanaMobileWalletAdapter, SolanaMobileWalletAdapter, SolanaMobileWalletAdapterRemoteWalletName, SolanaMobileWalletAdapterWalletName, createDefaultAddressSelector, createDefaultAuthorizationResultCache, createDefaultWalletNotFoundHandler };
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/adapter.ts","../../src/createDefaultAddressSelector.ts","../../src/createDefaultAuthorizationResultCache.ts","../../src/createDefaultWalletNotFoundHandler.ts"],"mappings":";;;;;;;UA8DiB,wBAAA;EACb,KAAA,IAAS,OAAA;EACT,GAAA,IAAO,OAAA,CAAQ,mBAAA,GAAsB,aAAA;EACrC,GAAA,CAAI,mBAAA,EAAqB,mBAAA,GAAsB,aAAA,GAAgB,OAAA;AAAA;AAAA,UAGlD,eAAA;EACb,MAAA,CAAO,SAAA,EAAW,oBAAA,KAAyB,OAAA,CAAQ,oBAAA;AAAA;AAAA,cAG1C,mCAAA,EAAkE,UAAA;AAAA,cAClE,yCAAA,EAA8E,UAAA;AAAA,uBA2B5E,6BAAA,SAAsC,oCAAA;EAAA;WACxC,4BAAA,EAA8B,GAAA,CAAI,kBAAA;EAI3C,IAAA,EAAI,UAAA;EACJ,IAAA;EACA,GAAA;EAAA,UAyBS,WAAA,CACL,MAAA,EAAQ,oCAAA,GAAuC,qCAAA,EAC/C,MAAA;IACI,eAAA,EAAiB,eAAA;IACjB,KAAA,EAAO,KAAA;EAAA;EAAA,IAkCX,SAAA,CAAA,GAAa,SAAA;EAAA,IAWb,SAAA,CAAA;EAAA,IAIA,UAAA,CAAA;EAAA,IAIA,UAAA,CAAA,GAAc,gBAAA;EA5H6C;EAiIzD,2CAAA,CAAA,GAA+C,OAAA;EAI/C,WAAA,CAAA,GAAe,OAAA;EAIf,OAAA,CAAA,GAAW,OAAA;EAtIW;EA8JtB,oBAAA,CAAqB,aAAA,GAAgB,aAAA,GAAgB,OAAA,CAAQ,mBAAA;EAkB7D,UAAA,CAAA,GAAc,OAAA;EAWd,MAAA,CAAO,KAAA,GAAQ,iBAAA,GAAoB,OAAA,CAAQ,kBAAA;EAwB3C,WAAA,CAAY,OAAA,EAAS,UAAA,GAAa,OAAA,CAAQ,UAAA;EAe1C,eAAA,WAA0B,WAAA,GAAoB,oBAAA,CAAA,CAChD,WAAA,EAAa,CAAA,EACb,UAAA,EAAY,UAAA,EACZ,OAAA,GAAU,WAAA,GACX,OAAA,CAAQ,oBAAA;EA6EL,eAAA,WAA0B,WAAA,GAAoB,oBAAA,CAAA,CAAsB,WAAA,EAAa,CAAA,GAAI,OAAA,CAAQ,CAAA;EAO7F,mBAAA,WAA8B,WAAA,GAAoB,oBAAA,CAAA,CAAsB,YAAA,EAAc,CAAA,KAAM,OAAA,CAAQ,CAAA;AAAA;AAAA,cA6DjG,8BAAA,SAAuC,6BAAA;EAtXL;;;cA0X/B,MAAA;IACR,eAAA,EAAiB,eAAA;IACjB,WAAA,EAAa,WAAA;IACb,wBAAA,EAA0B,wBAAA;IAC1B,OAAA,EAAS,OAAA;IACT,gBAAA,GAAmB,mBAAA,EAAqB,8BAAA,KAAmC,OAAA;EAAA;cAGnE,MAAA;IACR,eAAA,EAAiB,eAAA;IACjB,WAAA,EAAa,WAAA;IACb,wBAAA,EAA0B,wBAAA;IAC1B,KAAA,EAAO,KAAA;IACP,gBAAA,GAAmB,mBAAA,EAAqB,8BAAA,KAAmC,OAAA;EAAA;AAAA;AAAA,cAoCtE,+BAAA,SAAwC,6BAAA;cACrC,MAAA;IACR,eAAA,EAAiB,eAAA;IACjB,WAAA,EAAa,WAAA;IACb,wBAAA,EAA0B,wBAAA;IAC1B,KAAA,EAAO,KAAA;IACP,mBAAA;IACA,gBAAA,GAAmB,mBAAA,EAAqB,+BAAA,KAAoC,OAAA;EAAA;AAAA;AAAA,cA4BvE,yBAAA,SAAkC,8BAAA;;;iBCjhBvB,4BAAA,CAAA,GAAgC,eAAA;;;iBCEhC,qCAAA,CAAA,GAAyC,wBAAA;;;iBCIzC,kCAAA,CAAA,IACpB,mBAAA,EAAqB,yBAAA,KACpB,OAAA"}

View File

@@ -0,0 +1,80 @@
{
"name": "@solana-mobile/wallet-adapter-mobile",
"description": "An adapter for mobile wallet apps that conform to the Solana Mobile Wallet Adapter protocol",
"version": "2.2.8",
"author": "Steven Luscher <steven.luscher@solanamobile.com>",
"repository": {
"type": "git",
"url": "git+https://github.com/solana-mobile/mobile-wallet-adapter.git"
},
"license": "Apache-2.0",
"exports": {
"edge-light": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"workerd": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"browser": {
"import": "./lib/esm/index.browser.js",
"require": "./lib/cjs/index.browser.js"
},
"node": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"react-native": "./lib/cjs/index.native.js",
"types": "./lib/types/index.d.ts"
},
"browser": {
"./lib/cjs/index.js": "./lib/cjs/index.browser.js",
"./lib/esm/index.js": "./lib/esm/index.browser.js"
},
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"react-native": "lib/cjs/index.native.js",
"types": "lib/types/index.d.ts",
"type": "module",
"files": [
"lib",
"LICENSE"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@solana/web3.js": "^1.98.4",
"react-native": ">0.74"
},
"dependencies": {
"@solana/wallet-adapter-base": "^0.9.27",
"@solana/wallet-standard-features": "^1.3.0",
"@wallet-standard/core": "^1.1.1",
"bs58": "^6.0.0",
"js-base64": "^3.7.5",
"tslib": "^2.8.1",
"@solana-mobile/mobile-wallet-adapter-protocol": "^2.2.8",
"@solana-mobile/mobile-wallet-adapter-protocol-web3js": "^2.2.8",
"@solana-mobile/wallet-standard-mobile": "^0.5.2"
},
"optionalDependencies": {
"@react-native-async-storage/async-storage": "^1.17.7"
},
"devDependencies": {
"@solana/web3.js": "^1.98.4",
"@types/react-native": "^0.69.3",
"agadoo": "^3.0.0",
"cross-env": "^10.1.0",
"shx": "^0.4.0"
},
"scripts": {
"clean": "shx rm -rf lib/*",
"build": "pnpm clean && tsdown --config ../../tsdown.config.ts",
"build:watch": "pnpm clean && tsdown --config ../../tsdown.config.ts --watch",
"check-types": "tsc -p tsconfig.json --noEmit",
"postbuild": "printf '%s' '{\"type\":\"commonjs\"}' > lib/cjs/package.json && printf '%s' '{\"type\":\"module\"}' > lib/esm/package.json"
}
}

View File

@@ -0,0 +1,13 @@
Copyright 2025 Solana Mobile Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,3 @@
# `@solana-mobile/wallet-standard-mobile`
This is a plugin that registers a [`@wallet-standard/wallet-standard`](https://github.com/wallet-standard/wallet-standard) for mobile wallets. It enables web apps to use a native wallet app on a mobile device to sign messages and transactions, and to send transactions if the wallet offers support for sending transactions.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"commonjs"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"type":"module"}

View File

@@ -0,0 +1,95 @@
import { SolanaSignAndSendTransactionFeature, SolanaSignInFeature, SolanaSignMessageFeature, SolanaSignTransactionFeature } from "@solana/wallet-standard-features";
import { AppIdentity, AuthorizationResult, GetCapabilitiesAPI } from "@solana-mobile/mobile-wallet-adapter-protocol";
import { IdentifierArray, IdentifierString, Wallet, WalletAccount } from "@wallet-standard/base";
import { StandardConnectFeature, StandardDisconnectFeature, StandardEventsFeature } from "@wallet-standard/features";
//#region src/wallet.d.ts
type WalletCapabilities = Awaited<ReturnType<GetCapabilitiesAPI['getCapabilities']>>;
type Authorization = AuthorizationResult & Readonly<{
chain: IdentifierString;
capabilities: WalletCapabilities;
}>;
interface AuthorizationCache {
clear(): Promise<void>;
get(): Promise<Authorization | undefined>;
set(authorization: Authorization): Promise<void>;
}
interface ChainSelector {
select(chains: IdentifierArray): Promise<IdentifierString>;
}
declare const SolanaMobileWalletAdapterWalletName = "Mobile Wallet Adapter";
declare const SolanaMobileWalletAdapterRemoteWalletName = "Remote Mobile Wallet Adapter";
interface SolanaMobileWalletAdapterWallet extends Wallet {
url: string;
}
interface SolanaMobileWalletAdapterAuthorization {
get isAuthorized(): boolean;
get currentAuthorization(): Authorization | undefined;
get cachedAuthorizationResult(): Promise<Authorization | undefined>;
}
declare class LocalSolanaMobileWalletAdapterWallet implements SolanaMobileWalletAdapterWallet, SolanaMobileWalletAdapterAuthorization {
#private;
get version(): "1.0.0";
get name(): string;
get url(): string;
get icon(): `data:image/svg+xml;base64,${string}` | `data:image/webp;base64,${string}` | `data:image/png;base64,${string}` | `data:image/gif;base64,${string}`;
get chains(): IdentifierArray;
get features(): StandardConnectFeature & StandardDisconnectFeature & StandardEventsFeature & SolanaSignMessageFeature & SolanaSignInFeature & (SolanaSignAndSendTransactionFeature | SolanaSignTransactionFeature);
get accounts(): WalletAccount[];
constructor(config: {
appIdentity: AppIdentity;
authorizationCache: AuthorizationCache;
chains: IdentifierArray;
chainSelector: ChainSelector;
onWalletNotFound: (mobileWalletAdapter: SolanaMobileWalletAdapterWallet) => Promise<void>;
});
get connected(): boolean;
get isAuthorized(): boolean;
get currentAuthorization(): Authorization | undefined;
get cachedAuthorizationResult(): Promise<Authorization | undefined>;
}
declare class RemoteSolanaMobileWalletAdapterWallet implements SolanaMobileWalletAdapterWallet, SolanaMobileWalletAdapterAuthorization {
#private;
get version(): "1.0.0";
get name(): string;
get url(): string;
get icon(): `data:image/svg+xml;base64,${string}` | `data:image/webp;base64,${string}` | `data:image/png;base64,${string}` | `data:image/gif;base64,${string}`;
get chains(): IdentifierArray;
get features(): StandardConnectFeature & StandardDisconnectFeature & StandardEventsFeature & SolanaSignMessageFeature & SolanaSignInFeature & (SolanaSignAndSendTransactionFeature | SolanaSignTransactionFeature);
get accounts(): WalletAccount[];
constructor(config: {
appIdentity: AppIdentity;
authorizationCache: AuthorizationCache;
chains: IdentifierArray;
chainSelector: ChainSelector;
remoteHostAuthority: string;
onWalletNotFound: (mobileWalletAdapter: SolanaMobileWalletAdapterWallet) => Promise<void>;
});
get connected(): boolean;
get isAuthorized(): boolean;
get currentAuthorization(): Authorization | undefined;
get cachedAuthorizationResult(): Promise<Authorization | undefined>;
}
//#endregion
//#region src/initialize.d.ts
declare function registerMwa(config: {
appIdentity: AppIdentity;
authorizationCache: AuthorizationCache;
chains: IdentifierArray;
chainSelector: ChainSelector;
remoteHostAuthority?: string;
onWalletNotFound: (mobileWalletAdapter: SolanaMobileWalletAdapterWallet) => Promise<void>;
}): void;
//#endregion
//#region src/createDefaultWalletNotFoundHandler.d.ts
declare function defaultErrorModalWalletNotFoundHandler(): Promise<void>;
declare function createDefaultWalletNotFoundHandler(): (mobileWalletAdapter: SolanaMobileWalletAdapterWallet) => Promise<void>;
//#endregion
//#region src/createDefaultAuthorizationCache.d.ts
declare function createDefaultAuthorizationCache(): AuthorizationCache;
//#endregion
//#region src/createDefaultChainSelector.d.ts
declare function createDefaultChainSelector(): ChainSelector;
//#endregion
export { Authorization, AuthorizationCache, ChainSelector, LocalSolanaMobileWalletAdapterWallet, RemoteSolanaMobileWalletAdapterWallet, SolanaMobileWalletAdapterRemoteWalletName, SolanaMobileWalletAdapterWallet, SolanaMobileWalletAdapterWalletName, createDefaultAuthorizationCache, createDefaultChainSelector, createDefaultWalletNotFoundHandler, defaultErrorModalWalletNotFoundHandler, registerMwa };
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/wallet.ts","../../src/initialize.ts","../../src/createDefaultWalletNotFoundHandler.ts","../../src/createDefaultAuthorizationCache.ts","../../src/createDefaultChainSelector.ts"],"mappings":";;;;;;KAqDK,kBAAA,GAAqB,OAAA,CAAQ,UAAA,CAAW,kBAAA;AAAA,KAEjC,aAAA,GAAgB,mBAAA,GACxB,QAAA;EACI,KAAA,EAAO,gBAAA;EACP,YAAA,EAAc,kBAAA;AAAA;AAAA,UAGL,kBAAA;EACb,KAAA,IAAS,OAAA;EACT,GAAA,IAAO,OAAA,CAAQ,aAAA;EACf,GAAA,CAAI,aAAA,EAAe,aAAA,GAAgB,OAAA;AAAA;AAAA,UAGtB,aAAA;EACb,MAAA,CAAO,MAAA,EAAQ,eAAA,GAAkB,OAAA,CAAQ,gBAAA;AAAA;AAAA,cAGhC,mCAAA;AAAA,cACA,yCAAA;AAAA,UAeI,+BAAA,SAAwC,MAAA;EACrD,GAAA;AAAA;AAAA,UAGM,sCAAA;EAAA,IACF,YAAA;EAAA,IACA,oBAAA,IAAwB,aAAA;EAAA,IACxB,yBAAA,IAA6B,OAAA,CAAQ,aAAA;AAAA;AAAA,cAGhC,oCAAA,YACE,+BAAA,EAAiC,sCAAA;EAAA;MAuBxC,OAAA,CAAA;EAAA,IAIA,IAAA,CAAA;EAAA,IAIA,GAAA,CAAA;EAAA,IAIA,IAAA,CAAA;EAAA,IAIA,MAAA,CAAA,GAAM,eAAA;EAAA,IAIN,QAAA,CAAA,GAAY,sBAAA,GACZ,yBAAA,GACA,qBAAA,GACA,wBAAA,GACA,mBAAA,IACC,mCAAA,GAAsC,4BAAA;EAAA,IA0BvC,QAAA,CAAA,GAAQ,aAAA;cAIA,MAAA;IACR,WAAA,EAAa,WAAA;IACb,kBAAA,EAAoB,kBAAA;IACpB,MAAA,EAAQ,eAAA;IACR,aAAA,EAAe,aAAA;IACf,gBAAA,GAAmB,mBAAA,EAAqB,+BAAA,KAAoC,OAAA;EAAA;EAAA,IA0B5E,SAAA,CAAA;EAAA,IAIA,YAAA,CAAA;EAAA,IAIA,oBAAA,CAAA,GAAwB,aAAA;EAAA,IAIxB,yBAAA,CAAA,GAA6B,OAAA,CAAQ,aAAA;AAAA;AAAA,cA6XhC,qCAAA,YACE,+BAAA,EAAiC,sCAAA;EAAA;MAyBxC,OAAA,CAAA;EAAA,IAIA,IAAA,CAAA;EAAA,IAIA,GAAA,CAAA;EAAA,IAIA,IAAA,CAAA;EAAA,IAIA,MAAA,CAAA,GAAM,eAAA;EAAA,IAIN,QAAA,CAAA,GAAY,sBAAA,GACZ,yBAAA,GACA,qBAAA,GACA,wBAAA,GACA,mBAAA,IACC,mCAAA,GAAsC,4BAAA;EAAA,IA0BvC,QAAA,CAAA,GAAQ,aAAA;cAIA,MAAA;IACR,WAAA,EAAa,WAAA;IACb,kBAAA,EAAoB,kBAAA;IACpB,MAAA,EAAQ,eAAA;IACR,aAAA,EAAe,aAAA;IACf,mBAAA;IACA,gBAAA,GAAmB,mBAAA,EAAqB,+BAAA,KAAoC,OAAA;EAAA;EAAA,IA2B5E,SAAA,CAAA;EAAA,IAIA,YAAA,CAAA;EAAA,IAIA,oBAAA,CAAA,GAAwB,aAAA;EAAA,IAIxB,yBAAA,CAAA,GAA6B,OAAA,CAAQ,aAAA;AAAA;;;iBCpsB7B,WAAA,CAAY,MAAA;EACxB,WAAA,EAAa,WAAA;EACb,kBAAA,EAAoB,kBAAA;EACpB,MAAA,EAAQ,eAAA;EACR,aAAA,EAAe,aAAA;EACf,mBAAA;EACA,gBAAA,GAAmB,mBAAA,EAAqB,+BAAA,KAAoC,OAAA;AAAA;;;iBChB1D,sCAAA,CAAA,GAAsC,OAAA;AAAA,iBA0CpC,kCAAA,CAAA,IACpB,mBAAA,EAAqB,+BAAA,KACpB,OAAA;;;iBC9CmB,+BAAA,CAAA,GAAmC,kBAAA;;;iBCFnC,0BAAA,CAAA,GAA8B,aAAA"}

View File

@@ -0,0 +1,73 @@
{
"name": "@solana-mobile/wallet-standard-mobile",
"description": "A wallet-standard wallet for mobile wallet apps that conform to the Solana Mobile Wallet Adapter protocol",
"version": "0.5.2",
"author": "Marco Martinez <marco.martinez@solana.com>",
"repository": "https://github.com/solana-mobile/mobile-wallet-adapter",
"license": "Apache-2.0",
"exports": {
"edge-light": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"workerd": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"browser": {
"import": "./lib/esm/index.browser.js",
"require": "./lib/cjs/index.browser.js"
},
"node": {
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"react-native": "./lib/cjs/index.native.js",
"types": "./lib/types/index.d.ts"
},
"browser": {
"./lib/cjs/index.js": "./lib/cjs/index.browser.js",
"./lib/esm/index.js": "./lib/esm/index.browser.js"
},
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"react-native": "lib/cjs/index.native.js",
"types": "lib/types/index.d.ts",
"type": "module",
"files": [
"lib",
"LICENSE"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"dependencies": {
"@solana/wallet-standard-chains": "^1.1.1",
"@solana/wallet-standard-features": "^1.3.0",
"@wallet-standard/base": "^1.0.1",
"@wallet-standard/features": "^1.0.3",
"@wallet-standard/wallet": "^1.1.0",
"bs58": "^6.0.0",
"js-base64": "^3.7.5",
"qrcode": "^1.5.4",
"tslib": "^2.8.1",
"@solana-mobile/mobile-wallet-adapter-protocol": "^2.2.8"
},
"optionalDependencies": {
"@react-native-async-storage/async-storage": "^1.17.7"
},
"devDependencies": {
"@types/qrcode": "^1.5.5",
"agadoo": "^3.0.0",
"cross-env": "^10.1.0",
"shx": "^0.4.0"
},
"scripts": {
"clean": "shx rm -rf lib/*",
"build": "pnpm clean && tsdown --config ../../tsdown.config.ts",
"build:watch": "pnpm clean && tsdown --config ../../tsdown.config.ts --watch",
"check-types": "tsc -p tsconfig.json --noEmit",
"postbuild": "printf '%s' '{\"type\":\"commonjs\"}' > lib/cjs/package.json && printf '%s' '{\"type\":\"module\"}' > lib/esm/package.json"
}
}