@solana-mobile/mobile-wallet-adapter-protocol 2.0.1 → 2.1.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +3 -3
- package/android/src/main/java/com/solanamobile/mobilewalletadapter/reactnative/SolanaMobileWalletAdapterModule.kt +17 -15
- package/lib/cjs/index.browser.js +290 -81
- package/lib/cjs/index.js +290 -81
- package/lib/cjs/index.native.js +180 -28
- package/lib/esm/index.browser.js +288 -82
- package/lib/esm/index.js +288 -82
- package/lib/types/index.browser.d.ts +68 -5
- package/lib/types/index.browser.d.ts.map +1 -1
- package/lib/types/index.d.ts +68 -5
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.native.d.ts +68 -5
- package/lib/types/index.native.d.ts.map +1 -1
- package/package.json +10 -2
- package/src/__forks__/react-native/base64Utils.ts +1 -0
- package/src/__forks__/react-native/transact.ts +92 -106
- package/src/base64Utils.ts +3 -0
- package/src/createMobileWalletProxy.ts +175 -0
- package/src/createSIWSMessage.ts +14 -0
- package/src/encryptedMessage.ts +60 -0
- package/src/errors.ts +95 -93
- package/src/getAssociateAndroidIntentURL.ts +57 -52
- package/src/jsonRpcMessage.ts +38 -81
- package/src/parseHelloRsp.ts +46 -44
- package/src/parseSessionProps.ts +33 -0
- package/src/transact.ts +266 -268
- package/src/types.ts +74 -4
package/android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ buildscript {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
dependencies {
|
|
11
|
-
classpath 'com.android.tools.build:gradle:
|
|
11
|
+
classpath 'com.android.tools.build:gradle:8.2.1'
|
|
12
12
|
// noinspection DifferentKotlinGradleVersion
|
|
13
13
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
14
|
}
|
|
@@ -132,9 +132,9 @@ def kotlin_version = getExtOrDefault('kotlinVersion')
|
|
|
132
132
|
dependencies {
|
|
133
133
|
//noinspection GradleDynamicVersion
|
|
134
134
|
implementation "com.facebook.react:react-native:+" // From node_modules
|
|
135
|
-
implementation "com.solanamobile:mobile-wallet-adapter-clientlib:
|
|
135
|
+
implementation "com.solanamobile:mobile-wallet-adapter-clientlib:2.0.0"
|
|
136
136
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
137
|
-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.
|
|
137
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
if (isNewArchitectureEnabled()) {
|
|
@@ -10,6 +10,7 @@ import com.solana.mobilewalletadapter.clientlib.protocol.JsonRpc20Client
|
|
|
10
10
|
import com.solana.mobilewalletadapter.clientlib.protocol.MobileWalletAdapterClient
|
|
11
11
|
import com.solana.mobilewalletadapter.clientlib.scenario.LocalAssociationIntentCreator
|
|
12
12
|
import com.solana.mobilewalletadapter.clientlib.scenario.LocalAssociationScenario
|
|
13
|
+
import com.solana.mobilewalletadapter.common.protocol.SessionProperties.ProtocolVersion
|
|
13
14
|
import com.solanamobile.mobilewalletadapter.reactnative.JSONSerializationUtils.convertJsonToMap
|
|
14
15
|
import com.solanamobile.mobilewalletadapter.reactnative.JSONSerializationUtils.convertMapToJson
|
|
15
16
|
import kotlinx.coroutines.*
|
|
@@ -38,9 +39,9 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
38
39
|
// Used to ensure that you can't start more than one session at a time.
|
|
39
40
|
private val mutex: Mutex = Mutex()
|
|
40
41
|
private var sessionState: SessionState? = null
|
|
42
|
+
private var associationResultCallback: ((Int) -> Unit)? = null
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
private val mActivityEventCallbacks = mutableMapOf<Int, (Int, Intent?) -> (Unit)>()
|
|
44
45
|
private val mActivityEventListener: ActivityEventListener =
|
|
45
46
|
object : BaseActivityEventListener() {
|
|
46
47
|
override fun onActivityResult(
|
|
@@ -49,10 +50,8 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
49
50
|
resultCode: Int,
|
|
50
51
|
data: Intent?
|
|
51
52
|
) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
mActivityEventCallbacks.remove(requestCode)
|
|
55
|
-
}
|
|
53
|
+
if (requestCode == REQUEST_LOCAL_ASSOCIATION)
|
|
54
|
+
associationResultCallback?.invoke(resultCode)
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
@@ -78,7 +77,7 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
78
77
|
localAssociation.port,
|
|
79
78
|
localAssociation.session
|
|
80
79
|
)
|
|
81
|
-
|
|
80
|
+
associationResultCallback = { resultCode ->
|
|
82
81
|
if (resultCode == Activity.RESULT_CANCELED) {
|
|
83
82
|
Log.d(name, "Local association cancelled by user, ending session")
|
|
84
83
|
promise.reject("Session not established: Local association cancelled by user",
|
|
@@ -86,10 +85,18 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
86
85
|
localAssociation.close()
|
|
87
86
|
}
|
|
88
87
|
}
|
|
88
|
+
currentActivity?.startActivityForResult(intent, REQUEST_LOCAL_ASSOCIATION)
|
|
89
|
+
?: throw NullPointerException("Could not find a current activity from which to launch a local association")
|
|
89
90
|
val client =
|
|
90
91
|
localAssociation.start().get(ASSOCIATION_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
91
92
|
sessionState = SessionState(client, localAssociation)
|
|
92
|
-
|
|
93
|
+
val sessionPropertiesMap: WritableMap = WritableNativeMap()
|
|
94
|
+
sessionPropertiesMap.putString("protocol_version",
|
|
95
|
+
when (localAssociation.session.sessionProperties.protocolVersion) {
|
|
96
|
+
ProtocolVersion.LEGACY -> "legacy"
|
|
97
|
+
ProtocolVersion.V1 -> "v1"
|
|
98
|
+
})
|
|
99
|
+
promise.resolve(sessionPropertiesMap)
|
|
93
100
|
} catch (e: ActivityNotFoundException) {
|
|
94
101
|
Log.e(name, "Found no installed wallet that supports the mobile wallet protocol", e)
|
|
95
102
|
cleanup()
|
|
@@ -129,6 +136,8 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
129
136
|
val userInfo = Arguments.createMap()
|
|
130
137
|
userInfo.putInt("jsonRpcErrorCode", cause.code)
|
|
131
138
|
promise.reject("JSON_RPC_ERROR", cause, userInfo)
|
|
139
|
+
} else if (cause is TimeoutException) {
|
|
140
|
+
promise.reject("Timed out waiting for response", e)
|
|
132
141
|
} else {
|
|
133
142
|
throw e
|
|
134
143
|
}
|
|
@@ -159,16 +168,9 @@ class SolanaMobileWalletAdapterModule(reactContext: ReactApplicationContext) :
|
|
|
159
168
|
}
|
|
160
169
|
} ?: throw NullPointerException("Tried to end a session without an active session")
|
|
161
170
|
|
|
162
|
-
private fun launchIntent(intent: Intent, requestCode: Int, callback: (Int, Intent?) -> (Unit)) {
|
|
163
|
-
mActivityEventCallbacks[requestCode] = callback
|
|
164
|
-
currentActivity?.startActivityForResult(intent, REQUEST_LOCAL_ASSOCIATION) ?: run {
|
|
165
|
-
mActivityEventCallbacks.remove(requestCode)
|
|
166
|
-
throw NullPointerException("Could not find a current activity from which to launch a local association")
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
171
|
private fun cleanup() {
|
|
171
172
|
sessionState = null
|
|
173
|
+
associationResultCallback = null
|
|
172
174
|
if (mutex.isLocked) {
|
|
173
175
|
mutex.unlock()
|
|
174
176
|
}
|
package/lib/cjs/index.browser.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var walletStandardUtil = require('@solana/wallet-standard-util');
|
|
6
|
+
|
|
5
7
|
// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
|
|
6
8
|
const SolanaMobileWalletAdapterErrorCode = {
|
|
7
9
|
ERROR_ASSOCIATION_PORT_OUT_OF_RANGE: 'ERROR_ASSOCIATION_PORT_OUT_OF_RANGE',
|
|
@@ -10,6 +12,7 @@ const SolanaMobileWalletAdapterErrorCode = {
|
|
|
10
12
|
ERROR_SESSION_CLOSED: 'ERROR_SESSION_CLOSED',
|
|
11
13
|
ERROR_SESSION_TIMEOUT: 'ERROR_SESSION_TIMEOUT',
|
|
12
14
|
ERROR_WALLET_NOT_FOUND: 'ERROR_WALLET_NOT_FOUND',
|
|
15
|
+
ERROR_INVALID_PROTOCOL_VERSION: 'ERROR_INVALID_PROTOCOL_VERSION',
|
|
13
16
|
};
|
|
14
17
|
class SolanaMobileWalletAdapterError extends Error {
|
|
15
18
|
constructor(...args) {
|
|
@@ -77,6 +80,175 @@ function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
|
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
function encode(input) {
|
|
84
|
+
return window.btoa(input);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createSIWSMessage(payload) {
|
|
88
|
+
return walletStandardUtil.createSignInMessageText(payload);
|
|
89
|
+
}
|
|
90
|
+
function createSIWSMessageBase64(payload) {
|
|
91
|
+
return encode(createSIWSMessage(payload));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// optional features
|
|
95
|
+
const SolanaSignTransactions = 'solana:signTransactions';
|
|
96
|
+
const SolanaCloneAuthorization = 'solana:cloneAuthorization';
|
|
97
|
+
const SolanaSignInWithSolana = 'solana:signInWithSolana';
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Creates a {@link MobileWallet} proxy that handles backwards compatibility and API to RPC conversion.
|
|
101
|
+
*
|
|
102
|
+
* @param protocolVersion the protocol version in use for this session/request
|
|
103
|
+
* @param protocolRequestHandler callback function that handles sending the RPC request to the wallet endpoint.
|
|
104
|
+
* @returns a {@link MobileWallet} proxy
|
|
105
|
+
*/
|
|
106
|
+
function createMobileWalletProxy(protocolVersion, protocolRequestHandler) {
|
|
107
|
+
return new Proxy({}, {
|
|
108
|
+
get(target, p) {
|
|
109
|
+
if (target[p] == null) {
|
|
110
|
+
target[p] = function (inputParams) {
|
|
111
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
112
|
+
const { method, params } = handleMobileWalletRequest(p, inputParams, protocolVersion);
|
|
113
|
+
const result = yield protocolRequestHandler(method, params);
|
|
114
|
+
// if the request tried to sign in but the wallet did not return a sign in result, fallback on message signing
|
|
115
|
+
if (method === 'authorize' && params.sign_in_payload && !result.sign_in_result) {
|
|
116
|
+
result['sign_in_result'] = yield signInFallback(params.sign_in_payload, result, protocolRequestHandler);
|
|
117
|
+
}
|
|
118
|
+
return handleMobileWalletResponse(p, result, protocolVersion);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return target[p];
|
|
123
|
+
},
|
|
124
|
+
defineProperty() {
|
|
125
|
+
return false;
|
|
126
|
+
},
|
|
127
|
+
deleteProperty() {
|
|
128
|
+
return false;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Handles all {@link MobileWallet} API requests and determines the correct MWA RPC method and params to call.
|
|
134
|
+
* This handles backwards compatibility, based on the provided @protocolVersion.
|
|
135
|
+
*
|
|
136
|
+
* @param methodName the name of {@link MobileWallet} method that was called
|
|
137
|
+
* @param methodParams the parameters that were passed to the method
|
|
138
|
+
* @param protocolVersion the protocol version in use for this session/request
|
|
139
|
+
* @returns the RPC request method and params that should be sent to the wallet endpoint
|
|
140
|
+
*/
|
|
141
|
+
function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
|
|
142
|
+
let params = methodParams;
|
|
143
|
+
let method = methodName
|
|
144
|
+
.toString()
|
|
145
|
+
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
|
146
|
+
.toLowerCase();
|
|
147
|
+
switch (methodName) {
|
|
148
|
+
case 'authorize': {
|
|
149
|
+
let { chain } = params;
|
|
150
|
+
if (protocolVersion === 'legacy') {
|
|
151
|
+
switch (chain) {
|
|
152
|
+
case 'solana:testnet': {
|
|
153
|
+
chain = 'testnet';
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'solana:devnet': {
|
|
157
|
+
chain = 'devnet';
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'solana:mainnet': {
|
|
161
|
+
chain = 'mainnet-beta';
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
default: {
|
|
165
|
+
chain = params.cluster;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
params.cluster = chain;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
switch (chain) {
|
|
172
|
+
case 'testnet':
|
|
173
|
+
case 'devnet': {
|
|
174
|
+
chain = `solana:${chain}`;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'mainnet-beta': {
|
|
178
|
+
chain = 'solana:mainnet';
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
params.chain = chain;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
case 'reauthorize': {
|
|
186
|
+
const { auth_token, identity } = params;
|
|
187
|
+
if (auth_token) {
|
|
188
|
+
switch (protocolVersion) {
|
|
189
|
+
case 'legacy': {
|
|
190
|
+
method = 'reauthorize';
|
|
191
|
+
params = { auth_token: auth_token, identity: identity };
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
default: {
|
|
195
|
+
method = 'authorize';
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { method, params };
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Handles all {@link MobileWallet} API responses and modifies the response for backwards compatibility, if needed
|
|
207
|
+
*
|
|
208
|
+
* @param method the {@link MobileWallet} method that was called
|
|
209
|
+
* @param response the original response that was returned by the method call
|
|
210
|
+
* @param protocolVersion the protocol version in use for this session/request
|
|
211
|
+
* @returns the possibly modified response
|
|
212
|
+
*/
|
|
213
|
+
function handleMobileWalletResponse(method, response, protocolVersion) {
|
|
214
|
+
switch (method) {
|
|
215
|
+
case 'getCapabilities': {
|
|
216
|
+
const capabilities = response;
|
|
217
|
+
switch (protocolVersion) {
|
|
218
|
+
case 'legacy': {
|
|
219
|
+
const features = [SolanaSignTransactions];
|
|
220
|
+
if (capabilities.supports_clone_authorization === true) {
|
|
221
|
+
features.push(SolanaCloneAuthorization);
|
|
222
|
+
}
|
|
223
|
+
return Object.assign(Object.assign({}, capabilities), { features: features });
|
|
224
|
+
}
|
|
225
|
+
case 'v1': {
|
|
226
|
+
return Object.assign(Object.assign({}, capabilities), { supports_sign_and_send_transactions: true, supports_clone_authorization: capabilities.features.includes(SolanaCloneAuthorization) });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return response;
|
|
232
|
+
}
|
|
233
|
+
function signInFallback(signInPayload, authorizationResult, protocolRequestHandler) {
|
|
234
|
+
var _a;
|
|
235
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
236
|
+
const domain = (_a = signInPayload.domain) !== null && _a !== void 0 ? _a : window.location.host;
|
|
237
|
+
const address = authorizationResult.accounts[0].address;
|
|
238
|
+
const siwsMessage = createSIWSMessageBase64(Object.assign(Object.assign({}, signInPayload), { domain, address }));
|
|
239
|
+
const signMessageResult = yield protocolRequestHandler('sign_messages', {
|
|
240
|
+
addresses: [address],
|
|
241
|
+
payloads: [siwsMessage]
|
|
242
|
+
});
|
|
243
|
+
const signInResult = {
|
|
244
|
+
address: address,
|
|
245
|
+
signed_message: siwsMessage,
|
|
246
|
+
signature: signMessageResult.signed_payloads[0].slice(siwsMessage.length)
|
|
247
|
+
};
|
|
248
|
+
return signInResult;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
80
252
|
const SEQUENCE_NUMBER_BYTES = 4;
|
|
81
253
|
function createSequenceNumberVector(sequenceNumber) {
|
|
82
254
|
if (sequenceNumber >= 4294967296) {
|
|
@@ -88,29 +260,11 @@ function createSequenceNumberVector(sequenceNumber) {
|
|
|
88
260
|
return new Uint8Array(byteArray);
|
|
89
261
|
}
|
|
90
262
|
|
|
91
|
-
function generateAssociationKeypair() {
|
|
92
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
-
return yield crypto.subtle.generateKey({
|
|
94
|
-
name: 'ECDSA',
|
|
95
|
-
namedCurve: 'P-256',
|
|
96
|
-
}, false /* extractable */, ['sign'] /* keyUsages */);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function generateECDHKeypair() {
|
|
101
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
return yield crypto.subtle.generateKey({
|
|
103
|
-
name: 'ECDH',
|
|
104
|
-
namedCurve: 'P-256',
|
|
105
|
-
}, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
263
|
const INITIALIZATION_VECTOR_BYTES = 12;
|
|
110
|
-
|
|
264
|
+
const ENCODED_PUBLIC_KEY_LENGTH_BYTES = 65;
|
|
265
|
+
function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
|
|
111
266
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
|
-
const
|
|
113
|
-
const sequenceNumberVector = createSequenceNumberVector(jsonRpcMessage.id);
|
|
267
|
+
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
|
|
114
268
|
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
|
|
115
269
|
crypto.getRandomValues(initializationVector);
|
|
116
270
|
const ciphertext = yield crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, new TextEncoder().encode(plaintext));
|
|
@@ -121,18 +275,14 @@ function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
|
|
|
121
275
|
return response;
|
|
122
276
|
});
|
|
123
277
|
}
|
|
124
|
-
function
|
|
278
|
+
function decryptMessage(message, sharedSecret) {
|
|
125
279
|
return __awaiter(this, void 0, void 0, function* () {
|
|
126
280
|
const sequenceNumberVector = message.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
127
281
|
const initializationVector = message.slice(SEQUENCE_NUMBER_BYTES, SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
|
|
128
282
|
const ciphertext = message.slice(SEQUENCE_NUMBER_BYTES + INITIALIZATION_VECTOR_BYTES);
|
|
129
283
|
const plaintextBuffer = yield crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
|
|
130
284
|
const plaintext = getUtf8Decoder().decode(plaintextBuffer);
|
|
131
|
-
|
|
132
|
-
if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
|
|
133
|
-
throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
|
|
134
|
-
}
|
|
135
|
-
return jsonRpcMessage;
|
|
285
|
+
return plaintext;
|
|
136
286
|
});
|
|
137
287
|
}
|
|
138
288
|
function getAlgorithmParams(sequenceNumber, initializationVector) {
|
|
@@ -151,12 +301,48 @@ function getUtf8Decoder() {
|
|
|
151
301
|
return _utf8Decoder;
|
|
152
302
|
}
|
|
153
303
|
|
|
304
|
+
function generateAssociationKeypair() {
|
|
305
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
306
|
+
return yield crypto.subtle.generateKey({
|
|
307
|
+
name: 'ECDSA',
|
|
308
|
+
namedCurve: 'P-256',
|
|
309
|
+
}, false /* extractable */, ['sign'] /* keyUsages */);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function generateECDHKeypair() {
|
|
314
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
return yield crypto.subtle.generateKey({
|
|
316
|
+
name: 'ECDH',
|
|
317
|
+
namedCurve: 'P-256',
|
|
318
|
+
}, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
|
|
323
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
324
|
+
const plaintext = JSON.stringify(jsonRpcMessage);
|
|
325
|
+
const sequenceNumber = jsonRpcMessage.id;
|
|
326
|
+
return encryptMessage(plaintext, sequenceNumber, sharedSecret);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function decryptJsonRpcMessage(message, sharedSecret) {
|
|
330
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
331
|
+
const plaintext = yield decryptMessage(message, sharedSecret);
|
|
332
|
+
const jsonRpcMessage = JSON.parse(plaintext);
|
|
333
|
+
if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
|
|
334
|
+
throw new SolanaMobileWalletAdapterProtocolError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
|
|
335
|
+
}
|
|
336
|
+
return jsonRpcMessage;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
154
340
|
function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
|
|
155
341
|
associationPublicKey, ecdhPrivateKey) {
|
|
156
342
|
return __awaiter(this, void 0, void 0, function* () {
|
|
157
343
|
const [associationPublicKeyBuffer, walletPublicKey] = yield Promise.all([
|
|
158
344
|
crypto.subtle.exportKey('raw', associationPublicKey),
|
|
159
|
-
crypto.subtle.importKey('raw', payloadBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
|
|
345
|
+
crypto.subtle.importKey('raw', payloadBuffer.slice(0, ENCODED_PUBLIC_KEY_LENGTH_BYTES), { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
|
|
160
346
|
]);
|
|
161
347
|
const sharedSecret = yield crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
|
|
162
348
|
const ecdhSecretKey = yield crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
|
|
@@ -170,6 +356,31 @@ associationPublicKey, ecdhPrivateKey) {
|
|
|
170
356
|
});
|
|
171
357
|
}
|
|
172
358
|
|
|
359
|
+
function parseSessionProps(message, sharedSecret) {
|
|
360
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
361
|
+
const plaintext = yield decryptMessage(message, sharedSecret);
|
|
362
|
+
const jsonProperties = JSON.parse(plaintext);
|
|
363
|
+
let protocolVersion = 'legacy';
|
|
364
|
+
if (Object.hasOwnProperty.call(jsonProperties, 'v')) {
|
|
365
|
+
switch (jsonProperties.v) {
|
|
366
|
+
case 1:
|
|
367
|
+
case '1':
|
|
368
|
+
case 'v1':
|
|
369
|
+
protocolVersion = 'v1';
|
|
370
|
+
break;
|
|
371
|
+
case 'legacy':
|
|
372
|
+
protocolVersion = 'legacy';
|
|
373
|
+
break;
|
|
374
|
+
default:
|
|
375
|
+
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_INVALID_PROTOCOL_VERSION, `Unknown/unsupported protocol version: ${jsonProperties.v}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return ({
|
|
379
|
+
protocol_version: protocolVersion
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
173
384
|
function getRandomAssociationPort() {
|
|
174
385
|
return assertAssociationPort(49152 + Math.floor(Math.random() * (65535 - 49152 + 1)));
|
|
175
386
|
}
|
|
@@ -226,7 +437,7 @@ function getIntentURL(methodPathname, intentUrlBase) {
|
|
|
226
437
|
[...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join('/');
|
|
227
438
|
return new URL(pathname, baseUrl);
|
|
228
439
|
}
|
|
229
|
-
function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase) {
|
|
440
|
+
function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ['v1']) {
|
|
230
441
|
return __awaiter(this, void 0, void 0, function* () {
|
|
231
442
|
const associationPort = assertAssociationPort(putativePort);
|
|
232
443
|
const exportedKey = yield crypto.subtle.exportKey('raw', associationPublicKey);
|
|
@@ -234,6 +445,9 @@ function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associ
|
|
|
234
445
|
const url = getIntentURL('v1/associate/local', associationURLBase);
|
|
235
446
|
url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
|
|
236
447
|
url.searchParams.set('port', `${associationPort}`);
|
|
448
|
+
protocolVersions.forEach((version) => {
|
|
449
|
+
url.searchParams.set('v', version);
|
|
450
|
+
});
|
|
237
451
|
return url;
|
|
238
452
|
});
|
|
239
453
|
}
|
|
@@ -438,59 +652,51 @@ function transact(callback, config) {
|
|
|
438
652
|
break;
|
|
439
653
|
case 'hello_req_sent': {
|
|
440
654
|
const sharedSecret = yield parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
.toLowerCase();
|
|
449
|
-
target[p] = function (params) {
|
|
450
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
451
|
-
const id = nextJsonRpcMessageId++;
|
|
452
|
-
socket.send(yield encryptJsonRpcMessage({
|
|
453
|
-
id,
|
|
454
|
-
jsonrpc: '2.0',
|
|
455
|
-
method,
|
|
456
|
-
params: params !== null && params !== void 0 ? params : {},
|
|
457
|
-
}, sharedSecret));
|
|
458
|
-
return new Promise((resolve, reject) => {
|
|
459
|
-
jsonRpcResponsePromises[id] = {
|
|
460
|
-
resolve(result) {
|
|
461
|
-
switch (p) {
|
|
462
|
-
case 'authorize':
|
|
463
|
-
case 'reauthorize': {
|
|
464
|
-
const { wallet_uri_base } = result;
|
|
465
|
-
if (wallet_uri_base != null) {
|
|
466
|
-
try {
|
|
467
|
-
assertSecureEndpointSpecificURI(wallet_uri_base);
|
|
468
|
-
}
|
|
469
|
-
catch (e) {
|
|
470
|
-
reject(e);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
break;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
resolve(result);
|
|
478
|
-
},
|
|
479
|
-
reject,
|
|
480
|
-
};
|
|
481
|
-
});
|
|
482
|
-
});
|
|
483
|
-
};
|
|
655
|
+
const sessionPropertiesBuffer = responseBuffer.slice(ENCODED_PUBLIC_KEY_LENGTH_BYTES);
|
|
656
|
+
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0
|
|
657
|
+
? yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
658
|
+
const sequenceNumberVector = sessionPropertiesBuffer.slice(0, SEQUENCE_NUMBER_BYTES);
|
|
659
|
+
const sequenceNumber = getSequenceNumberFromByteArray(sequenceNumberVector);
|
|
660
|
+
if (sequenceNumber !== (lastKnownInboundSequenceNumber + 1)) {
|
|
661
|
+
throw new Error('Encrypted message has invalid sequence number');
|
|
484
662
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
663
|
+
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
664
|
+
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
|
|
665
|
+
}))() : { protocol_version: 'legacy' };
|
|
666
|
+
state = { __type: 'connected', sharedSecret, sessionProperties };
|
|
667
|
+
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, (method, params) => __awaiter(this, void 0, void 0, function* () {
|
|
668
|
+
const id = nextJsonRpcMessageId++;
|
|
669
|
+
socket.send(yield encryptJsonRpcMessage({
|
|
670
|
+
id,
|
|
671
|
+
jsonrpc: '2.0',
|
|
672
|
+
method,
|
|
673
|
+
params: params !== null && params !== void 0 ? params : {},
|
|
674
|
+
}, sharedSecret));
|
|
675
|
+
return new Promise((resolve, reject) => {
|
|
676
|
+
jsonRpcResponsePromises[id] = {
|
|
677
|
+
resolve(result) {
|
|
678
|
+
switch (method) {
|
|
679
|
+
case 'authorize':
|
|
680
|
+
case 'reauthorize': {
|
|
681
|
+
const { wallet_uri_base } = result;
|
|
682
|
+
if (wallet_uri_base != null) {
|
|
683
|
+
try {
|
|
684
|
+
assertSecureEndpointSpecificURI(wallet_uri_base);
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
reject(e);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
resolve(result);
|
|
695
|
+
},
|
|
696
|
+
reject,
|
|
697
|
+
};
|
|
698
|
+
});
|
|
699
|
+
}));
|
|
494
700
|
try {
|
|
495
701
|
resolve(yield callback(wallet));
|
|
496
702
|
}
|
|
@@ -533,8 +739,11 @@ function transact(callback, config) {
|
|
|
533
739
|
});
|
|
534
740
|
}
|
|
535
741
|
|
|
742
|
+
exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
|
|
536
743
|
exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
|
|
537
744
|
exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
|
|
538
745
|
exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
|
|
539
746
|
exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
|
|
747
|
+
exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
|
|
748
|
+
exports.SolanaSignTransactions = SolanaSignTransactions;
|
|
540
749
|
exports.transact = transact;
|