@meshconnect/uwc-wallet-connect-connector 0.6.3 → 0.6.4
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/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +2 -7
- package/dist/services/connection-service.js.map +1 -1
- package/dist/services/solana/solana-methods.d.ts +8 -0
- package/dist/services/solana/solana-methods.d.ts.map +1 -0
- package/dist/services/solana/solana-methods.js +10 -0
- package/dist/services/solana/solana-methods.js.map +1 -0
- package/dist/services/solana/solana-transaction-service.d.ts +6 -0
- package/dist/services/solana/solana-transaction-service.d.ts.map +1 -1
- package/dist/services/solana/solana-transaction-service.js +73 -52
- package/dist/services/solana/solana-transaction-service.js.map +1 -1
- package/package.json +1 -1
- package/src/services/connection-service.ts +2 -7
- package/src/services/solana/solana-methods.ts +9 -0
- package/src/services/solana/solana-transaction-service.test.ts +288 -0
- package/src/services/solana/solana-transaction-service.ts +112 -71
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-service.d.ts","sourceRoot":"","sources":["../../src/services/connection-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EACV,OAAO,EAEP,eAAe,EACf,qBAAqB,EAEtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"connection-service.d.ts","sourceRoot":"","sources":["../../src/services/connection-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EACV,OAAO,EAEP,eAAe,EACf,qBAAqB,EAEtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAK5D;;GAEG;AACH,qBAAa,iBAAiB;IAMhB,OAAO,CAAC,mBAAmB;IALvC,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,aAAa,CAAsB;gBAEvB,mBAAmB,EAAE,mBAAmB;IAE5D;;;OAGG;IACG,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,eAAe,CAAC;IAuD3B;;OAEG;IACH,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAIjC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA+FnC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA+CxB;;OAEG;IACH,UAAU,IAAI,YAAY,CAAC,MAAM,GAAG,IAAI;IAIxC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,MAAM,GAAG,IAAI;IAI9C;;OAEG;IACH,gBAAgB,IAAI,OAAO,GAAG,IAAI;IAIlC;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIxC;;OAEG;IACH,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAIjC;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CASlC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WalletConnectClient } from './walletconnect-client';
|
|
2
2
|
import { parseAccount, getNamespaceFromNetworkId } from '../utils/account-utils';
|
|
3
3
|
import { parseError } from '../utils/error-utils';
|
|
4
|
+
import { SOLANA_WC_METHODS } from './solana/solana-methods';
|
|
4
5
|
/**
|
|
5
6
|
* Service for handling WalletConnect connections
|
|
6
7
|
*/
|
|
@@ -104,13 +105,7 @@ export class ConnectionService {
|
|
|
104
105
|
case 'solana':
|
|
105
106
|
namespaces['solana'] = {
|
|
106
107
|
chains,
|
|
107
|
-
methods:
|
|
108
|
-
'solana_signMessage',
|
|
109
|
-
'solana_signTransaction',
|
|
110
|
-
'solana_signAndSendTransaction',
|
|
111
|
-
'solana_getAccounts',
|
|
112
|
-
'solana_requestAccounts'
|
|
113
|
-
],
|
|
108
|
+
methods: Object.values(SOLANA_WC_METHODS),
|
|
114
109
|
events: []
|
|
115
110
|
};
|
|
116
111
|
break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-service.js","sourceRoot":"","sources":["../../src/services/connection-service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAA;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"connection-service.js","sourceRoot":"","sources":["../../src/services/connection-service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAA;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAE3D;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAMR;IALZ,cAAc,GAA+B,IAAI,CAAA;IACjD,aAAa,GAAkB,IAAI,CAAA;IACnC,aAAa,GAAmB,IAAI,CAAA;IACpC,aAAa,GAAkB,IAAI,CAAA;IAE3C,YAAoB,mBAAwC;QAAxC,wBAAmB,GAAnB,mBAAmB,CAAqB;IAAG,CAAC;IAEhE;;;OAGG;IACH,KAAK,CAAC,OAAO,CACX,OAAgB,EAChB,QAA+B;QAE/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,CAAA;YAEzD,6DAA6D;YAC7D,MAAM,kBAAkB,GAAG,IAAI,CAAC,2BAA2B,CAAC,QAAQ,CAAC,CAAA;YAErE,2DAA2D;YAC3D,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;gBACzC,kBAAkB;aACnB,CAAC,CAAA;YAEF,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;YAChD,CAAC;YAED,wEAAwE;YACxE,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC,GAAG,CAAA;YAEtC,+CAA+C;YAC/C,sEAAsE;YACtE,MAAM,eAAe,GAAG,aAAa,CAAC,QAAQ,EAAE,CAAA;YAEhD,IAAI,CAAC;gBACH,iEAAiE;gBACjE,MAAM,OAAO,GAAG,MAAM,eAAe,CAAA;gBACrC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAA;gBAC7B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA,CAAC,6BAA6B;gBAEvD,iCAAiC;gBACjC,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAC3D,OAAO,EACP,OAAO,EACP,QAAQ,CACT,CAAA;gBAED,uCAAuC;gBACvC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAA;gBAC5B,IAAI,CAAC,aAAa,GAAG,OAAO,CAAA;gBAE5B,OAAO;oBACL,SAAS,EAAE,OAAO,CAAC,EAAE;oBACrB,OAAO;oBACP,kBAAkB;iBACnB,CAAA;YACH,CAAC;YAAC,OAAO,aAAa,EAAE,CAAC;gBACvB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA,CAAC,qBAAqB;gBAC/C,MAAM,aAAa,CAAA;YACrB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA,CAAC,qBAAqB;YAC/C,MAAM,UAAU,CAAC,KAAK,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;IAED;;OAEG;IACK,2BAA2B,CAAC,QAA+B;QAQjE,MAAM,UAAU,GAOZ,EAAE,CAAA;QAEN,8BAA8B;QAC9B,MAAM,eAAe,GAAgC,EAGpD,CAAA;QAED,QAAQ,CAAC,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;YAC/C,MAAM,SAAS,GAAG,yBAAyB,CAAC,SAAS,CAAC,CAAA;YACtD,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChC,eAAe,CAAC,SAAS,CAAC,GAAG,EAAE,CAAA;YACjC,CAAC;YACD,eAAe,CAAC,SAAS,CAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC7C,CAAC,CAAC,CAAA;QAEF,kCAAkC;QAClC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE;YAC9D,QAAQ,SAAS,EAAE,CAAC;gBAClB,KAAK,QAAQ;oBACX,UAAU,CAAC,QAAQ,CAAC,GAAG;wBACrB,MAAM;wBACN,OAAO,EAAE;4BACP,eAAe;4BACf,UAAU;4BACV,mBAAmB;4BACnB,sBAAsB;4BACtB,qBAAqB;4BACrB,wBAAwB;4BACxB,cAAc;4BACd,qBAAqB;4BACrB,4BAA4B;4BAC5B,yBAAyB;yBAC1B;wBACD,MAAM,EAAE,CAAC,cAAc,EAAE,iBAAiB,CAAC;qBAC5C,CAAA;oBACD,MAAK;gBAEP,KAAK,QAAQ;oBACX,UAAU,CAAC,QAAQ,CAAC,GAAG;wBACrB,MAAM;wBACN,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC;wBACzC,MAAM,EAAE,EAAE;qBACX,CAAA;oBACD,MAAK;gBAEP,KAAK,QAAQ;oBACX,UAAU,CAAC,QAAQ,CAAC,GAAG;wBACrB,MAAM;wBACN,OAAO,EAAE;4BACP,cAAc;4BACd,qBAAqB;4BACrB,UAAU;4BACV,aAAa;yBACd;wBACD,MAAM,EAAE,CAAC,yBAAyB,CAAC;qBACpC,CAAA;oBACD,MAAK;gBAEP,KAAK,MAAM;oBACT,UAAU,CAAC,MAAM,CAAC,GAAG;wBACnB,MAAM;wBACN,OAAO,EAAE,CAAC,sBAAsB,EAAE,kBAAkB,CAAC;wBACrD,MAAM,EAAE,EAAE;qBACX,CAAA;oBACD,MAAK;gBAEP;oBACE,gDAAgD;oBAChD,UAAU,CAAC,SAAS,CAAC,GAAG;wBACtB,MAAM;wBACN,OAAO,EAAE,EAAE;wBACX,MAAM,EAAE,EAAE;qBACX,CAAA;YACL,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,OAAO,UAAU,CAAA;IACnB,CAAC;IAED;;OAEG;IACK,gBAAgB,CACtB,OAA4B,EAC5B,OAAgB,EAChB,QAA+B;QAK/B,MAAM,kBAAkB,GAAuB,EAAE,CAAA;QACjD,IAAI,cAAc,GAAG,EAAE,CAAA;QAEvB,wCAAwC;QACxC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE;YACtD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAe,EAAE,EAAE;gBACxC,IAAI,CAAC;oBACH,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;oBAEpD,yEAAyE;oBACzE,IAAI,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;wBACrD,kBAAkB,CAAC,IAAI,CAAC;4BACtB,OAAO;4BACP,SAAS;yBACV,CAAC,CAAA;wBAEF,yDAAyD;wBACzD,IAAI,SAAS,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;4BAChD,cAAc,GAAG,OAAO,CAAA;wBAC1B,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,+BAA+B;gBACjC,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,kEAAkE;QAClE,IAAI,CAAC,cAAc,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,cAAc,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAA;QACvD,CAAC;QAED,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;QAClD,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAA;IACxD,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,cAAc,CAAA;IAC5B,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,OAA4B;QACrC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAA;IAC/B,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,OAAgB;QAC/B,IAAI,CAAC,aAAa,GAAG,OAAO,CAAA;IAC9B,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,OAAe;QAC9B,IAAI,CAAC,aAAa,GAAG,OAAO,CAAA;IAC9B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;YACpE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const SOLANA_WC_METHODS: {
|
|
2
|
+
readonly signMessage: "solana_signMessage";
|
|
3
|
+
readonly signTransaction: "solana_signTransaction";
|
|
4
|
+
readonly signAndSendTransaction: "solana_signAndSendTransaction";
|
|
5
|
+
readonly getAccounts: "solana_getAccounts";
|
|
6
|
+
readonly requestAccounts: "solana_requestAccounts";
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=solana-methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana-methods.d.ts","sourceRoot":"","sources":["../../../src/services/solana/solana-methods.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Single source for the Solana JSON-RPC method names used over WalletConnect, so the methods
|
|
2
|
+
// we advertise in the session proposal and the ones we request can't drift apart.
|
|
3
|
+
export const SOLANA_WC_METHODS = {
|
|
4
|
+
signMessage: 'solana_signMessage',
|
|
5
|
+
signTransaction: 'solana_signTransaction',
|
|
6
|
+
signAndSendTransaction: 'solana_signAndSendTransaction',
|
|
7
|
+
getAccounts: 'solana_getAccounts',
|
|
8
|
+
requestAccounts: 'solana_requestAccounts'
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=solana-methods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana-methods.js","sourceRoot":"","sources":["../../../src/services/solana/solana-methods.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,kFAAkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,WAAW,EAAE,oBAAoB;IACjC,eAAe,EAAE,wBAAwB;IACzC,sBAAsB,EAAE,+BAA+B;IACvD,WAAW,EAAE,oBAAoB;IACjC,eAAe,EAAE,wBAAwB;CACjC,CAAA"}
|
|
@@ -13,6 +13,12 @@ export declare class SolanaTransactionService {
|
|
|
13
13
|
private sendNativeTransaction;
|
|
14
14
|
private sendSplTokenTransaction;
|
|
15
15
|
private submitTransaction;
|
|
16
|
+
/**
|
|
17
|
+
* Fallback for wallets that don't support `solana_signAndSendTransaction`: ask the wallet
|
|
18
|
+
* to sign only, then broadcast the signed tx via our own RPC. Used both when the pre-check
|
|
19
|
+
* sees the method isn't approved and when an approved signAndSend is rejected at runtime.
|
|
20
|
+
*/
|
|
21
|
+
private signTransactionAndBroadcast;
|
|
16
22
|
/**
|
|
17
23
|
* Sign Solana tx bytes over WalletConnect WITHOUT broadcasting (relay submits).
|
|
18
24
|
* Bytes in/out (legacy or v0); only deserializes if the wallet returns a bare
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solana-transaction-service.d.ts","sourceRoot":"","sources":["../../../src/services/solana/solana-transaction-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EAIb,wBAAwB,EACzB,MAAM,wBAAwB,CAAA;AAW/B,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"solana-transaction-service.d.ts","sourceRoot":"","sources":["../../../src/services/solana/solana-transaction-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EAIb,wBAAwB,EACzB,MAAM,wBAAwB,CAAA;AAW/B,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAkBxD,qBAAa,wBAAwB;IAGvB,OAAO,CAAC,aAAa;IAFjC,OAAO,CAAC,OAAO,CAA0B;gBAErB,aAAa,GAAE,aAAkB;IAIrD;;OAEG;IACG,eAAe,CACnB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,YAAY,CAAC,MAAM,EAC5B,OAAO,EAAE,wBAAwB,EACjC,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;YAsCJ,mCAAmC;YAuCnC,qBAAqB;YAsCrB,uBAAuB;YAuCvB,iBAAiB;IA0E/B;;;;OAIG;YACW,2BAA2B;IA0DzC;;;;OAIG;IACG,oBAAoB,CACxB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,YAAY,CAAC,MAAM,EAC5B,YAAY,EAAE,UAAU,EACxB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,UAAU,CAAC;CAoDvB"}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { assertSameMessage, isVersionedTransactionBytes } from '@meshconnect/uwc-types';
|
|
2
2
|
import { VersionedTransaction, PublicKey, Connection, Transaction } from '@solana/web3.js';
|
|
3
|
+
import { isValidNamespacesRequest } from '@walletconnect/utils';
|
|
4
|
+
import { SOLANA_WC_METHODS } from './solana-methods';
|
|
3
5
|
import { standardizeSignature } from '../../utils/encoding-utils';
|
|
4
6
|
import { parseError } from '../../utils/error-utils';
|
|
5
7
|
import { Buffer } from 'buffer';
|
|
6
8
|
import base58 from 'bs58';
|
|
7
9
|
import { SolanaTransactionBuilder } from './solana-transaction-builder';
|
|
10
|
+
// ed25519 signatures are 64 bytes — reject a malformed one with a clear error.
|
|
11
|
+
function assertSolanaSignatureLength(signature) {
|
|
12
|
+
if (signature.length !== 64) {
|
|
13
|
+
throw new Error('WalletConnect returned a malformed Solana signature (expected 64 bytes)');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
8
16
|
export class SolanaTransactionService {
|
|
9
17
|
networkRpcMap;
|
|
10
18
|
builder;
|
|
@@ -114,66 +122,82 @@ export class SolanaTransactionService {
|
|
|
114
122
|
else {
|
|
115
123
|
serializedTransaction = Buffer.from(new Uint8Array(transaction.serialize())).toString('base64');
|
|
116
124
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
method: 'solana_signAndSendTransaction',
|
|
124
|
-
params: {
|
|
125
|
-
transaction: serializedTransaction
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
// WalletConnect returns the signature directly for Solana
|
|
130
|
-
return result;
|
|
131
|
-
}
|
|
132
|
-
catch (signAndSendError) {
|
|
133
|
-
const errorMessage = signAndSendError instanceof Error
|
|
134
|
-
? signAndSendError.message
|
|
135
|
-
: String(signAndSendError);
|
|
136
|
-
// If signAndSendTransaction is not supported, fallback to signTransaction
|
|
137
|
-
if (errorMessage.includes('solana_signAndSendTransaction') ||
|
|
138
|
-
errorMessage.includes('Missing or invalid')) {
|
|
139
|
-
// Sign the transaction only
|
|
125
|
+
// Only request signAndSend when the wallet approved it for this chain. isValidNamespacesRequest
|
|
126
|
+
// is the SignClient's own keying-robust approved-methods check.
|
|
127
|
+
const walletApprovedSignAndSend = isValidNamespacesRequest(session.namespaces, chainId, SOLANA_WC_METHODS.signAndSendTransaction);
|
|
128
|
+
if (walletApprovedSignAndSend) {
|
|
129
|
+
try {
|
|
130
|
+
// Preferred path: the wallet broadcasts and returns the signature directly.
|
|
140
131
|
const result = await client.request({
|
|
141
132
|
topic: session.topic,
|
|
142
133
|
chainId,
|
|
143
134
|
request: {
|
|
144
|
-
method:
|
|
135
|
+
method: SOLANA_WC_METHODS.signAndSendTransaction,
|
|
145
136
|
params: {
|
|
146
137
|
transaction: serializedTransaction
|
|
147
138
|
}
|
|
148
139
|
}
|
|
149
140
|
});
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
catch (signAndSendError) {
|
|
144
|
+
const errorMessage = signAndSendError instanceof Error
|
|
145
|
+
? signAndSendError.message
|
|
146
|
+
: String(signAndSendError);
|
|
147
|
+
// Only swallow WalletConnect's not-approved rejection for signAndSend specifically:
|
|
148
|
+
// "Missing or invalid. request() method: solana_signAndSendTransaction". Requiring both
|
|
149
|
+
// parts lets unrelated "Missing or invalid" errors (params/chain) and user rejections
|
|
150
|
+
// that merely echo the method name surface instead of triggering a second prompt.
|
|
151
|
+
const isSignAndSendNotApproved = errorMessage.includes('Missing or invalid') &&
|
|
152
|
+
errorMessage.includes(SOLANA_WC_METHODS.signAndSendTransaction);
|
|
153
|
+
if (!isSignAndSendNotApproved) {
|
|
154
|
+
throw signAndSendError;
|
|
153
155
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
156
|
+
// fall through to sign-then-broadcast as a safety net
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return this.signTransactionAndBroadcast(client, session, transaction, activeNetworkId, activeAddress, serializedTransaction);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Fallback for wallets that don't support `solana_signAndSendTransaction`: ask the wallet
|
|
163
|
+
* to sign only, then broadcast the signed tx via our own RPC. Used both when the pre-check
|
|
164
|
+
* sees the method isn't approved and when an approved signAndSend is rejected at runtime.
|
|
165
|
+
*/
|
|
166
|
+
async signTransactionAndBroadcast(client, session, transaction, activeNetworkId, activeAddress, serializedTransaction) {
|
|
167
|
+
const result = await client.request({
|
|
168
|
+
topic: session.topic,
|
|
169
|
+
chainId: activeNetworkId,
|
|
170
|
+
request: {
|
|
171
|
+
method: SOLANA_WC_METHODS.signTransaction,
|
|
172
|
+
params: {
|
|
173
|
+
transaction: serializedTransaction
|
|
162
174
|
}
|
|
163
|
-
// Get RPC URL from the network RPC map or fall back to public endpoint
|
|
164
|
-
const rpcUrl = this.networkRpcMap[activeNetworkId] ||
|
|
165
|
-
'https://alien-newest-vineyard.solana-mainnet.quiknode.pro/ebe5e35661d7edb7a5e48ab84bd9d477e472a40b';
|
|
166
|
-
const connection = new Connection(rpcUrl);
|
|
167
|
-
const rawSignature = await connection.sendRawTransaction(transaction.serialize(), {
|
|
168
|
-
skipPreflight: false,
|
|
169
|
-
maxRetries: 3,
|
|
170
|
-
preflightCommitment: 'confirmed'
|
|
171
|
-
});
|
|
172
|
-
return standardizeSignature(rawSignature);
|
|
173
175
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
});
|
|
177
|
+
if ('signature' in result) {
|
|
178
|
+
const decoded = base58.decode(result.signature);
|
|
179
|
+
assertSolanaSignatureLength(decoded);
|
|
180
|
+
transaction.addSignature(new PublicKey(activeAddress), Buffer.from(decoded));
|
|
181
|
+
}
|
|
182
|
+
if ('transaction' in result) {
|
|
183
|
+
const decodedTransaction = Buffer.from(result.transaction, 'base64');
|
|
184
|
+
if (transaction instanceof VersionedTransaction) {
|
|
185
|
+
transaction = VersionedTransaction.deserialize(new Uint8Array(decodedTransaction));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
transaction = Transaction.from(decodedTransaction);
|
|
189
|
+
}
|
|
176
190
|
}
|
|
191
|
+
// Get RPC URL from the network RPC map or fall back to public endpoint
|
|
192
|
+
const rpcUrl = this.networkRpcMap[activeNetworkId] ||
|
|
193
|
+
'https://alien-newest-vineyard.solana-mainnet.quiknode.pro/ebe5e35661d7edb7a5e48ab84bd9d477e472a40b';
|
|
194
|
+
const connection = new Connection(rpcUrl);
|
|
195
|
+
const rawSignature = await connection.sendRawTransaction(transaction.serialize(), {
|
|
196
|
+
skipPreflight: false,
|
|
197
|
+
maxRetries: 3,
|
|
198
|
+
preflightCommitment: 'confirmed'
|
|
199
|
+
});
|
|
200
|
+
return standardizeSignature(rawSignature);
|
|
177
201
|
}
|
|
178
202
|
/**
|
|
179
203
|
* Sign Solana tx bytes over WalletConnect WITHOUT broadcasting (relay submits).
|
|
@@ -188,7 +212,7 @@ export class SolanaTransactionService {
|
|
|
188
212
|
topic: session.topic,
|
|
189
213
|
chainId,
|
|
190
214
|
request: {
|
|
191
|
-
method:
|
|
215
|
+
method: SOLANA_WC_METHODS.signTransaction,
|
|
192
216
|
params: { transaction: serializedBase64 }
|
|
193
217
|
}
|
|
194
218
|
});
|
|
@@ -201,10 +225,7 @@ export class SolanaTransactionService {
|
|
|
201
225
|
// Bare signature back (legacy WC) — splice it into our original bytes.
|
|
202
226
|
if ('signature' in result) {
|
|
203
227
|
const signatureBytes = base58.decode(result.signature);
|
|
204
|
-
|
|
205
|
-
if (signatureBytes.length !== 64) {
|
|
206
|
-
throw new Error('WalletConnect returned a malformed Solana signature (expected 64 bytes)');
|
|
207
|
-
}
|
|
228
|
+
assertSolanaSignatureLength(signatureBytes);
|
|
208
229
|
const userPubkey = new PublicKey(activeAddress);
|
|
209
230
|
if (isVersionedTransactionBytes(serializedTx)) {
|
|
210
231
|
const tx = VersionedTransaction.deserialize(serializedTx);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solana-transaction-service.js","sourceRoot":"","sources":["../../../src/services/solana/solana-transaction-service.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"solana-transaction-service.js","sourceRoot":"","sources":["../../../src/services/solana/solana-transaction-service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { WalletConnectClient } from './walletconnect-client'
|
|
10
10
|
import { parseAccount, getNamespaceFromNetworkId } from '../utils/account-utils'
|
|
11
11
|
import { parseError } from '../utils/error-utils'
|
|
12
|
+
import { SOLANA_WC_METHODS } from './solana/solana-methods'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Service for handling WalletConnect connections
|
|
@@ -149,13 +150,7 @@ export class ConnectionService {
|
|
|
149
150
|
case 'solana':
|
|
150
151
|
namespaces['solana'] = {
|
|
151
152
|
chains,
|
|
152
|
-
methods:
|
|
153
|
-
'solana_signMessage',
|
|
154
|
-
'solana_signTransaction',
|
|
155
|
-
'solana_signAndSendTransaction',
|
|
156
|
-
'solana_getAccounts',
|
|
157
|
-
'solana_requestAccounts'
|
|
158
|
-
],
|
|
153
|
+
methods: Object.values(SOLANA_WC_METHODS),
|
|
159
154
|
events: []
|
|
160
155
|
}
|
|
161
156
|
break
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Single source for the Solana JSON-RPC method names used over WalletConnect, so the methods
|
|
2
|
+
// we advertise in the session proposal and the ones we request can't drift apart.
|
|
3
|
+
export const SOLANA_WC_METHODS = {
|
|
4
|
+
signMessage: 'solana_signMessage',
|
|
5
|
+
signTransaction: 'solana_signTransaction',
|
|
6
|
+
signAndSendTransaction: 'solana_signAndSendTransaction',
|
|
7
|
+
getAccounts: 'solana_getAccounts',
|
|
8
|
+
requestAccounts: 'solana_requestAccounts'
|
|
9
|
+
} as const
|
|
@@ -18,6 +18,23 @@ import base58 from 'bs58'
|
|
|
18
18
|
import type SignClient from '@walletconnect/sign-client'
|
|
19
19
|
import type { SessionTypes } from '@walletconnect/types'
|
|
20
20
|
|
|
21
|
+
// submitTransaction broadcasts the fallback-signed tx via a real Connection.
|
|
22
|
+
// Stub Connection.sendRawTransaction so the pre-check tests never hit the network; the
|
|
23
|
+
// rest of @solana/web3.js stays real (the sign/serialize round-trips the other suites
|
|
24
|
+
// rely on are untouched).
|
|
25
|
+
const { sendRawTransaction } = vi.hoisted(() => ({
|
|
26
|
+
sendRawTransaction: vi.fn()
|
|
27
|
+
}))
|
|
28
|
+
vi.mock('@solana/web3.js', async importOriginal => {
|
|
29
|
+
const actual = await importOriginal<typeof import('@solana/web3.js')>()
|
|
30
|
+
return {
|
|
31
|
+
...actual,
|
|
32
|
+
Connection: vi.fn(function () {
|
|
33
|
+
return { sendRawTransaction }
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
21
38
|
const SOL_CHAIN_ID = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
|
|
22
39
|
const MOCK_TOPIC = 'mock-session-topic'
|
|
23
40
|
|
|
@@ -400,3 +417,274 @@ describe('SolanaTransactionService — public method preserves PublicKey usage',
|
|
|
400
417
|
)
|
|
401
418
|
})
|
|
402
419
|
})
|
|
420
|
+
|
|
421
|
+
describe('SolanaTransactionService.submitTransaction — signAndSend pre-check', () => {
|
|
422
|
+
const blockhash = '11111111111111111111111111111111'
|
|
423
|
+
const activeAddress = userKeypair.publicKey.toBase58()
|
|
424
|
+
const nativeRequest = {
|
|
425
|
+
from: activeAddress,
|
|
426
|
+
to: recipient.toBase58(),
|
|
427
|
+
amount: 1n,
|
|
428
|
+
blockhash
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// The fully-signed legacy tx the mocked wallet returns for solana_signTransaction, so
|
|
432
|
+
// submitTransaction's broadcast branch can deserialize + (mock-)broadcast it.
|
|
433
|
+
function signedLegacyBase64(): string {
|
|
434
|
+
const tx = buildLegacyTx(blockhash)
|
|
435
|
+
tx.sign(userKeypair)
|
|
436
|
+
return tx.serialize().toString('base64')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Reject signAndSend exactly as the WC SignClient does when the method isn't in the
|
|
440
|
+
// approved namespace; resolve signTransaction with a signed tx.
|
|
441
|
+
function makeDualMethodClient(): {
|
|
442
|
+
request: ReturnType<typeof vi.fn>
|
|
443
|
+
client: SignClient
|
|
444
|
+
} {
|
|
445
|
+
const signed = signedLegacyBase64()
|
|
446
|
+
const request = vi
|
|
447
|
+
.fn()
|
|
448
|
+
.mockImplementation((args: { request: { method: string } }) => {
|
|
449
|
+
const method = args.request.method
|
|
450
|
+
if (method === 'solana_signAndSendTransaction') {
|
|
451
|
+
return Promise.reject(
|
|
452
|
+
new Error(
|
|
453
|
+
'Missing or invalid. request() method: solana_signAndSendTransaction'
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
if (method === 'solana_signTransaction') {
|
|
458
|
+
return Promise.resolve({ transaction: signed })
|
|
459
|
+
}
|
|
460
|
+
return Promise.reject(new Error(`unexpected method ${method}`))
|
|
461
|
+
})
|
|
462
|
+
return { request, client: { request } as unknown as SignClient }
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function sessionWithMethods(methods: string[]): SessionTypes.Struct {
|
|
466
|
+
return {
|
|
467
|
+
topic: MOCK_TOPIC,
|
|
468
|
+
namespaces: {
|
|
469
|
+
solana: {
|
|
470
|
+
accounts: [`${SOL_CHAIN_ID}:${activeAddress}`],
|
|
471
|
+
methods,
|
|
472
|
+
events: []
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} as unknown as SessionTypes.Struct
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
beforeEach(() => {
|
|
479
|
+
sendRawTransaction.mockReset()
|
|
480
|
+
sendRawTransaction.mockResolvedValue(base58.encode(new Uint8Array(64)))
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('does NOT attempt solana_signAndSendTransaction when the session never approved it', async () => {
|
|
484
|
+
// Trust Wallet / Exodus approve a Solana account but only solana_signTransaction.
|
|
485
|
+
// Attempting signAndSend anyway is what logs the noisy WC error in prod.
|
|
486
|
+
const { request, client } = makeDualMethodClient()
|
|
487
|
+
|
|
488
|
+
await new SolanaTransactionService().sendTransaction(
|
|
489
|
+
client,
|
|
490
|
+
sessionWithMethods(['solana_signTransaction']),
|
|
491
|
+
nativeRequest,
|
|
492
|
+
SOL_CHAIN_ID,
|
|
493
|
+
activeAddress
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
const methods = request.mock.calls.map(
|
|
497
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
498
|
+
)
|
|
499
|
+
expect(methods).not.toContain('solana_signAndSendTransaction')
|
|
500
|
+
expect(methods).toEqual(['solana_signTransaction'])
|
|
501
|
+
expect(sendRawTransaction).toHaveBeenCalledTimes(1)
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
it('DOES attempt solana_signAndSendTransaction when the session approved it', async () => {
|
|
505
|
+
// Phantom / Solflare approve signAndSend — preserve the wallet-broadcast path.
|
|
506
|
+
const request = vi.fn().mockResolvedValue('signature-from-wallet')
|
|
507
|
+
const client = { request } as unknown as SignClient
|
|
508
|
+
|
|
509
|
+
const result = await new SolanaTransactionService().sendTransaction(
|
|
510
|
+
client,
|
|
511
|
+
sessionWithMethods([
|
|
512
|
+
'solana_signTransaction',
|
|
513
|
+
'solana_signAndSendTransaction'
|
|
514
|
+
]),
|
|
515
|
+
nativeRequest,
|
|
516
|
+
SOL_CHAIN_ID,
|
|
517
|
+
activeAddress
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
const methods = request.mock.calls.map(
|
|
521
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
522
|
+
)
|
|
523
|
+
expect(methods).toEqual(['solana_signAndSendTransaction'])
|
|
524
|
+
expect(result).toBe('signature-from-wallet')
|
|
525
|
+
expect(sendRawTransaction).not.toHaveBeenCalled()
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('falls back to solana_signTransaction when an approved signAndSend is rejected at runtime', async () => {
|
|
529
|
+
// Safety net: a wallet that advertises signAndSend but rejects it must still recover.
|
|
530
|
+
const { request, client } = makeDualMethodClient()
|
|
531
|
+
|
|
532
|
+
await new SolanaTransactionService().sendTransaction(
|
|
533
|
+
client,
|
|
534
|
+
sessionWithMethods([
|
|
535
|
+
'solana_signTransaction',
|
|
536
|
+
'solana_signAndSendTransaction'
|
|
537
|
+
]),
|
|
538
|
+
nativeRequest,
|
|
539
|
+
SOL_CHAIN_ID,
|
|
540
|
+
activeAddress
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
const methods = request.mock.calls.map(
|
|
544
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
545
|
+
)
|
|
546
|
+
expect(methods).toEqual([
|
|
547
|
+
'solana_signAndSendTransaction',
|
|
548
|
+
'solana_signTransaction'
|
|
549
|
+
])
|
|
550
|
+
expect(sendRawTransaction).toHaveBeenCalledTimes(1)
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('rethrows a user rejection of an approved signAndSend even when the message echoes the method name', async () => {
|
|
554
|
+
// A wallet may reject signAndSend (user declined) with a message containing the method
|
|
555
|
+
// name. We must NOT fall back and re-prompt signTransaction — surface the rejection.
|
|
556
|
+
const request = vi
|
|
557
|
+
.fn()
|
|
558
|
+
.mockRejectedValueOnce(
|
|
559
|
+
new Error('User rejected the request: solana_signAndSendTransaction')
|
|
560
|
+
)
|
|
561
|
+
const client = { request } as unknown as SignClient
|
|
562
|
+
|
|
563
|
+
await expect(
|
|
564
|
+
new SolanaTransactionService().sendTransaction(
|
|
565
|
+
client,
|
|
566
|
+
sessionWithMethods([
|
|
567
|
+
'solana_signTransaction',
|
|
568
|
+
'solana_signAndSendTransaction'
|
|
569
|
+
]),
|
|
570
|
+
nativeRequest,
|
|
571
|
+
SOL_CHAIN_ID,
|
|
572
|
+
activeAddress
|
|
573
|
+
)
|
|
574
|
+
).rejects.toThrow(/User rejected/)
|
|
575
|
+
|
|
576
|
+
const methods = request.mock.calls.map(
|
|
577
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
578
|
+
)
|
|
579
|
+
expect(methods).toEqual(['solana_signAndSendTransaction'])
|
|
580
|
+
expect(sendRawTransaction).not.toHaveBeenCalled()
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('resolves approved methods for per-chain-keyed namespaces (solana:<id>), skipping signAndSend when absent', async () => {
|
|
584
|
+
// WalletConnect / CAIP-25 lets a session key namespaces per chain. The pre-check defers
|
|
585
|
+
// to isValidNamespacesRequest, which reads methods by chain id regardless of key shape —
|
|
586
|
+
// so a `solana:<id>` key that omits signAndSend is still detected. A plain
|
|
587
|
+
// `namespaces['solana']` lookup would miss it and wrongly attempt signAndSend.
|
|
588
|
+
const { request, client } = makeDualMethodClient()
|
|
589
|
+
|
|
590
|
+
const session = {
|
|
591
|
+
topic: MOCK_TOPIC,
|
|
592
|
+
namespaces: {
|
|
593
|
+
[SOL_CHAIN_ID]: {
|
|
594
|
+
accounts: [`${SOL_CHAIN_ID}:${activeAddress}`],
|
|
595
|
+
methods: ['solana_signTransaction'],
|
|
596
|
+
events: []
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} as unknown as SessionTypes.Struct
|
|
600
|
+
|
|
601
|
+
await new SolanaTransactionService().sendTransaction(
|
|
602
|
+
client,
|
|
603
|
+
session,
|
|
604
|
+
nativeRequest,
|
|
605
|
+
SOL_CHAIN_ID,
|
|
606
|
+
activeAddress
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
const methods = request.mock.calls.map(
|
|
610
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
611
|
+
)
|
|
612
|
+
expect(methods).toEqual(['solana_signTransaction'])
|
|
613
|
+
expect(methods).not.toContain('solana_signAndSendTransaction')
|
|
614
|
+
expect(sendRawTransaction).toHaveBeenCalledTimes(1)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
it('drives the SPL (VersionedTransaction) path through submitTransaction, skipping signAndSend when unapproved', async () => {
|
|
618
|
+
// submitTransaction serializes/deserializes both legacy (native) and VersionedTransaction
|
|
619
|
+
// (SPL / generic) inputs. SPL routes through the versioned branch, so this guards that
|
|
620
|
+
// path end-to-end through the pre-check, not just the native one.
|
|
621
|
+
const blockhash = '11111111111111111111111111111111'
|
|
622
|
+
const signedVersioned = buildVersionedTx(blockhash)
|
|
623
|
+
signedVersioned.sign([userKeypair])
|
|
624
|
+
const signedVersionedB64 = Buffer.from(
|
|
625
|
+
signedVersioned.serialize()
|
|
626
|
+
).toString('base64')
|
|
627
|
+
|
|
628
|
+
const request = vi
|
|
629
|
+
.fn()
|
|
630
|
+
.mockImplementation((args: { request: { method: string } }) => {
|
|
631
|
+
const method = args.request.method
|
|
632
|
+
if (method === 'solana_signAndSendTransaction') {
|
|
633
|
+
return Promise.reject(
|
|
634
|
+
new Error(
|
|
635
|
+
'Missing or invalid. request() method: solana_signAndSendTransaction'
|
|
636
|
+
)
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
if (method === 'solana_signTransaction') {
|
|
640
|
+
return Promise.resolve({ transaction: signedVersionedB64 })
|
|
641
|
+
}
|
|
642
|
+
return Promise.reject(new Error(`unexpected method ${method}`))
|
|
643
|
+
})
|
|
644
|
+
const client = { request } as unknown as SignClient
|
|
645
|
+
|
|
646
|
+
const splRequest = {
|
|
647
|
+
from: activeAddress,
|
|
648
|
+
to: recipient.toBase58(),
|
|
649
|
+
amount: 1n,
|
|
650
|
+
blockhash,
|
|
651
|
+
tokenMint: Keypair.generate().publicKey.toBase58(),
|
|
652
|
+
tokenDecimals: 6
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
await new SolanaTransactionService().sendTransaction(
|
|
656
|
+
client,
|
|
657
|
+
sessionWithMethods(['solana_signTransaction']),
|
|
658
|
+
splRequest,
|
|
659
|
+
SOL_CHAIN_ID,
|
|
660
|
+
activeAddress
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
const methods = request.mock.calls.map(
|
|
664
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
665
|
+
)
|
|
666
|
+
expect(methods).toEqual(['solana_signTransaction'])
|
|
667
|
+
expect(sendRawTransaction).toHaveBeenCalledTimes(1)
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
it('skips signAndSend when the session exposes no approved methods for the chain', async () => {
|
|
671
|
+
// Conservative default: if the session doesn't approve signAndSend (empty/missing methods),
|
|
672
|
+
// we don't optimistically try it — isValidNamespacesRequest returns false and we
|
|
673
|
+
// sign-then-broadcast. The SignClient would reject an optimistic attempt anyway.
|
|
674
|
+
const { request, client } = makeDualMethodClient()
|
|
675
|
+
|
|
676
|
+
await new SolanaTransactionService().sendTransaction(
|
|
677
|
+
client,
|
|
678
|
+
sessionWithMethods([]),
|
|
679
|
+
nativeRequest,
|
|
680
|
+
SOL_CHAIN_ID,
|
|
681
|
+
activeAddress
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
const methods = request.mock.calls.map(
|
|
685
|
+
c => (c[0] as { request: { method: string } }).request.method
|
|
686
|
+
)
|
|
687
|
+
expect(methods).toEqual(['solana_signTransaction'])
|
|
688
|
+
expect(sendRawTransaction).toHaveBeenCalledTimes(1)
|
|
689
|
+
})
|
|
690
|
+
})
|
|
@@ -18,12 +18,23 @@ import {
|
|
|
18
18
|
} from '@solana/web3.js'
|
|
19
19
|
import type SignClient from '@walletconnect/sign-client'
|
|
20
20
|
import type { SessionTypes } from '@walletconnect/types'
|
|
21
|
+
import { isValidNamespacesRequest } from '@walletconnect/utils'
|
|
22
|
+
import { SOLANA_WC_METHODS } from './solana-methods'
|
|
21
23
|
import { standardizeSignature } from '../../utils/encoding-utils'
|
|
22
24
|
import { parseError } from '../../utils/error-utils'
|
|
23
25
|
import { Buffer } from 'buffer'
|
|
24
26
|
import base58 from 'bs58'
|
|
25
27
|
import { SolanaTransactionBuilder } from './solana-transaction-builder'
|
|
26
28
|
|
|
29
|
+
// ed25519 signatures are 64 bytes — reject a malformed one with a clear error.
|
|
30
|
+
function assertSolanaSignatureLength(signature: Uint8Array): void {
|
|
31
|
+
if (signature.length !== 64) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'WalletConnect returned a malformed Solana signature (expected 64 bytes)'
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export class SolanaTransactionService {
|
|
28
39
|
private builder: SolanaTransactionBuilder
|
|
29
40
|
|
|
@@ -216,84 +227,119 @@ export class SolanaTransactionService {
|
|
|
216
227
|
).toString('base64')
|
|
217
228
|
}
|
|
218
229
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
230
|
+
// Only request signAndSend when the wallet approved it for this chain. isValidNamespacesRequest
|
|
231
|
+
// is the SignClient's own keying-robust approved-methods check.
|
|
232
|
+
const walletApprovedSignAndSend = isValidNamespacesRequest(
|
|
233
|
+
session.namespaces,
|
|
234
|
+
chainId,
|
|
235
|
+
SOLANA_WC_METHODS.signAndSendTransaction
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if (walletApprovedSignAndSend) {
|
|
239
|
+
try {
|
|
240
|
+
// Preferred path: the wallet broadcasts and returns the signature directly.
|
|
241
|
+
const result = await client.request({
|
|
242
|
+
topic: session.topic,
|
|
243
|
+
chainId,
|
|
244
|
+
request: {
|
|
245
|
+
method: SOLANA_WC_METHODS.signAndSendTransaction,
|
|
246
|
+
params: {
|
|
247
|
+
transaction: serializedTransaction
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
return result as string
|
|
253
|
+
} catch (signAndSendError: unknown) {
|
|
254
|
+
const errorMessage =
|
|
255
|
+
signAndSendError instanceof Error
|
|
256
|
+
? signAndSendError.message
|
|
257
|
+
: String(signAndSendError)
|
|
258
|
+
// Only swallow WalletConnect's not-approved rejection for signAndSend specifically:
|
|
259
|
+
// "Missing or invalid. request() method: solana_signAndSendTransaction". Requiring both
|
|
260
|
+
// parts lets unrelated "Missing or invalid" errors (params/chain) and user rejections
|
|
261
|
+
// that merely echo the method name surface instead of triggering a second prompt.
|
|
262
|
+
const isSignAndSendNotApproved =
|
|
263
|
+
errorMessage.includes('Missing or invalid') &&
|
|
264
|
+
errorMessage.includes(SOLANA_WC_METHODS.signAndSendTransaction)
|
|
265
|
+
if (!isSignAndSendNotApproved) {
|
|
266
|
+
throw signAndSendError
|
|
267
|
+
}
|
|
268
|
+
// fall through to sign-then-broadcast as a safety net
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return this.signTransactionAndBroadcast(
|
|
273
|
+
client,
|
|
274
|
+
session,
|
|
275
|
+
transaction,
|
|
276
|
+
activeNetworkId,
|
|
277
|
+
activeAddress,
|
|
278
|
+
serializedTransaction
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Fallback for wallets that don't support `solana_signAndSendTransaction`: ask the wallet
|
|
284
|
+
* to sign only, then broadcast the signed tx via our own RPC. Used both when the pre-check
|
|
285
|
+
* sees the method isn't approved and when an approved signAndSend is rejected at runtime.
|
|
286
|
+
*/
|
|
287
|
+
private async signTransactionAndBroadcast(
|
|
288
|
+
client: SignClient,
|
|
289
|
+
session: SessionTypes.Struct,
|
|
290
|
+
transaction: Transaction | VersionedTransaction,
|
|
291
|
+
activeNetworkId: string,
|
|
292
|
+
activeAddress: string,
|
|
293
|
+
serializedTransaction: string
|
|
294
|
+
): Promise<string> {
|
|
295
|
+
const result: { transaction: string } | { signature: string } =
|
|
296
|
+
await client.request({
|
|
222
297
|
topic: session.topic,
|
|
223
|
-
chainId,
|
|
298
|
+
chainId: activeNetworkId,
|
|
224
299
|
request: {
|
|
225
|
-
method:
|
|
300
|
+
method: SOLANA_WC_METHODS.signTransaction,
|
|
226
301
|
params: {
|
|
227
302
|
transaction: serializedTransaction
|
|
228
303
|
}
|
|
229
304
|
}
|
|
230
305
|
})
|
|
231
306
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
errorMessage.includes('solana_signAndSendTransaction') ||
|
|
242
|
-
errorMessage.includes('Missing or invalid')
|
|
243
|
-
) {
|
|
244
|
-
// Sign the transaction only
|
|
245
|
-
const result: { transaction: string } | { signature: string } =
|
|
246
|
-
await client.request({
|
|
247
|
-
topic: session.topic,
|
|
248
|
-
chainId,
|
|
249
|
-
request: {
|
|
250
|
-
method: 'solana_signTransaction',
|
|
251
|
-
params: {
|
|
252
|
-
transaction: serializedTransaction
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
if ('signature' in result) {
|
|
258
|
-
const decoded = base58.decode(result.signature)
|
|
259
|
-
transaction.addSignature(
|
|
260
|
-
new PublicKey(activeAddress),
|
|
261
|
-
Buffer.from(decoded) as Buffer & Uint8Array
|
|
262
|
-
)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if ('transaction' in result) {
|
|
266
|
-
const decodedTransaction = Buffer.from(result.transaction, 'base64')
|
|
307
|
+
if ('signature' in result) {
|
|
308
|
+
const decoded = base58.decode(result.signature)
|
|
309
|
+
assertSolanaSignatureLength(decoded)
|
|
310
|
+
transaction.addSignature(
|
|
311
|
+
new PublicKey(activeAddress),
|
|
312
|
+
Buffer.from(decoded) as Buffer & Uint8Array
|
|
313
|
+
)
|
|
314
|
+
}
|
|
267
315
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
new Uint8Array(decodedTransaction)
|
|
271
|
-
)
|
|
272
|
-
} else {
|
|
273
|
-
transaction = Transaction.from(decodedTransaction)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
316
|
+
if ('transaction' in result) {
|
|
317
|
+
const decodedTransaction = Buffer.from(result.transaction, 'base64')
|
|
276
318
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
'https://alien-newest-vineyard.solana-mainnet.quiknode.pro/ebe5e35661d7edb7a5e48ab84bd9d477e472a40b'
|
|
281
|
-
const connection = new Connection(rpcUrl)
|
|
282
|
-
const rawSignature = await connection.sendRawTransaction(
|
|
283
|
-
transaction.serialize(),
|
|
284
|
-
{
|
|
285
|
-
skipPreflight: false,
|
|
286
|
-
maxRetries: 3,
|
|
287
|
-
preflightCommitment: 'confirmed'
|
|
288
|
-
}
|
|
319
|
+
if (transaction instanceof VersionedTransaction) {
|
|
320
|
+
transaction = VersionedTransaction.deserialize(
|
|
321
|
+
new Uint8Array(decodedTransaction)
|
|
289
322
|
)
|
|
323
|
+
} else {
|
|
324
|
+
transaction = Transaction.from(decodedTransaction)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
290
327
|
|
|
291
|
-
|
|
328
|
+
// Get RPC URL from the network RPC map or fall back to public endpoint
|
|
329
|
+
const rpcUrl =
|
|
330
|
+
this.networkRpcMap[activeNetworkId as NetworkId] ||
|
|
331
|
+
'https://alien-newest-vineyard.solana-mainnet.quiknode.pro/ebe5e35661d7edb7a5e48ab84bd9d477e472a40b'
|
|
332
|
+
const connection = new Connection(rpcUrl)
|
|
333
|
+
const rawSignature = await connection.sendRawTransaction(
|
|
334
|
+
transaction.serialize(),
|
|
335
|
+
{
|
|
336
|
+
skipPreflight: false,
|
|
337
|
+
maxRetries: 3,
|
|
338
|
+
preflightCommitment: 'confirmed'
|
|
292
339
|
}
|
|
340
|
+
)
|
|
293
341
|
|
|
294
|
-
|
|
295
|
-
throw signAndSendError
|
|
296
|
-
}
|
|
342
|
+
return standardizeSignature(rawSignature)
|
|
297
343
|
}
|
|
298
344
|
|
|
299
345
|
/**
|
|
@@ -317,7 +363,7 @@ export class SolanaTransactionService {
|
|
|
317
363
|
topic: session.topic,
|
|
318
364
|
chainId,
|
|
319
365
|
request: {
|
|
320
|
-
method:
|
|
366
|
+
method: SOLANA_WC_METHODS.signTransaction,
|
|
321
367
|
params: { transaction: serializedBase64 }
|
|
322
368
|
}
|
|
323
369
|
})
|
|
@@ -334,12 +380,7 @@ export class SolanaTransactionService {
|
|
|
334
380
|
// Bare signature back (legacy WC) — splice it into our original bytes.
|
|
335
381
|
if ('signature' in result) {
|
|
336
382
|
const signatureBytes = base58.decode(result.signature) as Uint8Array
|
|
337
|
-
|
|
338
|
-
if (signatureBytes.length !== 64) {
|
|
339
|
-
throw new Error(
|
|
340
|
-
'WalletConnect returned a malformed Solana signature (expected 64 bytes)'
|
|
341
|
-
)
|
|
342
|
-
}
|
|
383
|
+
assertSolanaSignatureLength(signatureBytes)
|
|
343
384
|
const userPubkey = new PublicKey(activeAddress)
|
|
344
385
|
|
|
345
386
|
if (isVersionedTransactionBytes(serializedTx)) {
|