@meshconnect/uwc-types 0.13.0 → 0.14.0-snapshot.2c887a2
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/connector.d.ts +14 -1
- package/dist/connector.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/react-hooks.d.ts +39 -1
- package/dist/react-hooks.d.ts.map +1 -1
- package/dist/signature.d.ts +35 -0
- package/dist/signature.d.ts.map +1 -1
- package/dist/signature.js +39 -1
- package/dist/signature.js.map +1 -1
- package/dist/solana-message.d.ts +15 -0
- package/dist/solana-message.d.ts.map +1 -0
- package/dist/solana-message.js +60 -0
- package/dist/solana-message.js.map +1 -0
- package/package.json +1 -1
- package/src/connector.ts +22 -1
- package/src/index.ts +1 -0
- package/src/react-hooks.ts +45 -0
- package/src/signature.test.ts +197 -0
- package/src/signature.ts +59 -0
- package/src/solana-message.test.ts +111 -0
- package/src/solana-message.ts +67 -0
package/dist/connector.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Network, NetworkId, Namespace } from './networks';
|
|
|
2
2
|
import type { AvailableAddress } from './session';
|
|
3
3
|
import type { DetectedEIP6963WalletInfo, DetectedSolanaWalletInfo, DetectedTronWalletInfo, DetectedTonWalletInfo, ExtensionInjectedProvider, IntegratedBrowserInjectedProvider, WalletConnectProvider, TonConnectWalletProvider, WalletMetadata } from './UWC-state';
|
|
4
4
|
import type { EVMCapabilities, TransactionRequest, TransactionResult } from './transactions';
|
|
5
|
-
import type { SignatureType } from './signature';
|
|
5
|
+
import type { SignatureType, EIP712TypedData } from './signature';
|
|
6
6
|
/**
|
|
7
7
|
* Result interface for connect operations
|
|
8
8
|
*/
|
|
@@ -68,6 +68,13 @@ export interface Connector {
|
|
|
68
68
|
* @returns A promise that resolves to the signature
|
|
69
69
|
*/
|
|
70
70
|
signMessage?(message: string, provider?: ExtensionInjectedProvider | IntegratedBrowserInjectedProvider | WalletConnectProvider | TonConnectWalletProvider): Promise<SignatureType>;
|
|
71
|
+
/**
|
|
72
|
+
* Sign EIP-712 typed structured data (eth_signTypedData_v4).
|
|
73
|
+
* Required for ERC-3009 (Transfer With Authorization) and EIP-2612 (Permit) relay flows.
|
|
74
|
+
* EVM-only (eip155); connectors that don't support it leave this undefined.
|
|
75
|
+
* @returns A promise that resolves to the 65-byte hex signature (0x-prefixed)
|
|
76
|
+
*/
|
|
77
|
+
signTypedData?(typedData: EIP712TypedData, provider?: ExtensionInjectedProvider | IntegratedBrowserInjectedProvider | WalletConnectProvider): Promise<string>;
|
|
71
78
|
/**
|
|
72
79
|
* Send a transaction with the connected wallet
|
|
73
80
|
* @param request The transaction request parameters
|
|
@@ -76,5 +83,11 @@ export interface Connector {
|
|
|
76
83
|
*/
|
|
77
84
|
sendTransaction?(request: TransactionRequest, provider?: ExtensionInjectedProvider | IntegratedBrowserInjectedProvider | WalletConnectProvider | TonConnectWalletProvider): Promise<TransactionResult>;
|
|
78
85
|
getWalletCapabilities?(from: string, networks: Network[]): Promise<Record<string, EVMCapabilities>>;
|
|
86
|
+
/**
|
|
87
|
+
* Sign a Solana tx without broadcasting — raw bytes in/out (no @solana/web3.js
|
|
88
|
+
* for callers). Fee-payer relay flows: the wallet adds only the user's
|
|
89
|
+
* signature, the relay broadcasts. Optional; not all connectors support it.
|
|
90
|
+
*/
|
|
91
|
+
signSolanaTransactionBytes?(serializedTx: Uint8Array): Promise<Uint8Array>;
|
|
79
92
|
}
|
|
80
93
|
//# sourceMappingURL=connector.d.ts.map
|
package/dist/connector.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../src/connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,yBAAyB,EACzB,iCAAiC,EACjC,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACf,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../src/connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,yBAAyB,EACzB,iCAAiC,EACjC,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACf,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEjE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,kBAAkB,EAAE,gBAAgB,EAAE,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAA;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IAEH,OAAO,CACL,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EACL,yBAAyB,GACzB,iCAAiC,GACjC,qBAAqB,GACrB,wBAAwB,GAC3B,OAAO,CAAC,eAAe,CAAC,CAAA;IAE3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,MAAM,CAAA;IAE3B;;;;OAIG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5B;;;;;OAKG;IAEH,aAAa,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EACL,yBAAyB,GACzB,iCAAiC,GACjC,qBAAqB,GACrB,wBAAwB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAE/B;;;;;;OAMG;IACH,mBAAmB,CAAC,CAClB,SAAS,EAAE,SAAS,EACpB,eAAe,CAAC,EAAE,cAAc,EAAE,GACjC,OAAO,CACN,yBAAyB,EAAE,GAC3B,wBAAwB,EAAE,GAC1B,sBAAsB,EAAE,GACxB,qBAAqB,EAAE,CAC1B,CAAA;IAED;;;;;OAKG;IACH,WAAW,CAAC,CACV,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EACL,yBAAyB,GACzB,iCAAiC,GACjC,qBAAqB,GACrB,wBAAwB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAAA;IAEzB;;;;;OAKG;IACH,aAAa,CAAC,CACZ,SAAS,EAAE,eAAe,EAC1B,QAAQ,CAAC,EACL,yBAAyB,GACzB,iCAAiC,GACjC,qBAAqB,GACxB,OAAO,CAAC,MAAM,CAAC,CAAA;IAElB;;;;;OAKG;IACH,eAAe,CAAC,CACd,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,CAAC,EACL,yBAAyB,GACzB,iCAAiC,GACjC,qBAAqB,GACrB,wBAAwB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAE7B,qBAAqB,CAAC,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EAAE,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAA;IAE3C;;;;OAIG;IACH,0BAA0B,CAAC,CAAC,YAAY,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;CAC3E"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA"}
|
package/dist/react-hooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NetworkId, Network, ConnectionMode, Session, AvailableAddress, TransactionRequest, TransactionResult, WalletError, EVMCapabilities, SignatureType, WalletMetadata } from './index';
|
|
1
|
+
import type { NetworkId, Network, ConnectionMode, Session, AvailableAddress, TransactionRequest, TransactionResult, WalletError, EVMCapabilities, SignatureType, EIP712TypedData, WalletMetadata } from './index';
|
|
2
2
|
/**
|
|
3
3
|
* WalletConnect connection interface for the useConnection hook
|
|
4
4
|
*/
|
|
@@ -230,6 +230,44 @@ export interface UseSignMessageReturn {
|
|
|
230
230
|
*/
|
|
231
231
|
error: WalletError | undefined;
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Return type for the useSignTypedData hook.
|
|
235
|
+
* Provides EIP-712 typed data signing (eth_signTypedData_v4) for EVM wallets.
|
|
236
|
+
* Used by ERC-3009 (Transfer With Authorization) and EIP-2612 (Permit) relay flows.
|
|
237
|
+
*/
|
|
238
|
+
export interface UseSignTypedDataReturn {
|
|
239
|
+
/**
|
|
240
|
+
* Signs EIP-712 typed data with the connected EVM wallet
|
|
241
|
+
* @param typedData - The EIP-712 typed data to sign
|
|
242
|
+
* @returns Promise that resolves to the 65-byte hex signature (0x-prefixed)
|
|
243
|
+
* @throws Error if no wallet is connected, the wallet is non-EVM, or signing fails
|
|
244
|
+
*/
|
|
245
|
+
signTypedData: (typedData: EIP712TypedData) => Promise<string>;
|
|
246
|
+
/**
|
|
247
|
+
* Indicates if a typed-data signing operation is currently in progress
|
|
248
|
+
*/
|
|
249
|
+
isLoading: boolean;
|
|
250
|
+
/**
|
|
251
|
+
* The raw hex signature from the last successful sign, undefined if not yet signed
|
|
252
|
+
*/
|
|
253
|
+
signature: string | undefined;
|
|
254
|
+
/**
|
|
255
|
+
* Error from the last signing attempt, undefined if no error occurred
|
|
256
|
+
*/
|
|
257
|
+
error: WalletError | undefined;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Return type for useSignSolanaTransaction — sign-only Solana signing (bytes in,
|
|
261
|
+
* bytes out) for fee-payer relay flows where the relay, not the wallet, broadcasts.
|
|
262
|
+
* @example
|
|
263
|
+
* const signed = await signSolanaTransaction(unsignedBytes)
|
|
264
|
+
*/
|
|
265
|
+
export interface UseSignSolanaTransactionReturn {
|
|
266
|
+
/** Sign serialized tx bytes without broadcasting. Throws if no wallet / signing fails. */
|
|
267
|
+
signSolanaTransaction: (serializedTx: Uint8Array) => Promise<Uint8Array>;
|
|
268
|
+
isLoading: boolean;
|
|
269
|
+
error: WalletError | undefined;
|
|
270
|
+
}
|
|
233
271
|
/**
|
|
234
272
|
* Return type for the useTransaction hook
|
|
235
273
|
* Provides transaction sending functionality with loading state and result
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-hooks.d.ts","sourceRoot":"","sources":["../src/react-hooks.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react-hooks.d.ts","sourceRoot":"","sources":["../src/react-hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/signature.d.ts
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import type { ExtensionInjectedProvider, IntegratedBrowserInjectedProvider } from './UWC-state';
|
|
2
|
+
/**
|
|
3
|
+
* EIP-712 typed structured data for eth_signTypedData_v4.
|
|
4
|
+
* Used by ERC-3009 (Transfer With Authorization), EIP-2612 (Permit), and similar standards.
|
|
5
|
+
*/
|
|
6
|
+
export interface EIP712TypedData {
|
|
7
|
+
domain: {
|
|
8
|
+
name?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
chainId?: number;
|
|
11
|
+
verifyingContract?: string;
|
|
12
|
+
salt?: string;
|
|
13
|
+
};
|
|
14
|
+
types: Record<string, Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
type: string;
|
|
17
|
+
}>>;
|
|
18
|
+
primaryType: string;
|
|
19
|
+
message: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Ensures the EIP712Domain type entry is present in `types`, derived from the
|
|
23
|
+
* fields actually set on `domain`, in EIP-712's canonical order.
|
|
24
|
+
*
|
|
25
|
+
* Why: some wallets (notably Trust Wallet and several mobile wallets over
|
|
26
|
+
* WalletConnect) parse the `types` object strictly and reject an
|
|
27
|
+
* eth_signTypedData_v4 request that omits EIP712Domain. Desktop MetaMask derives
|
|
28
|
+
* it implicitly, so callers tend to leave it out — this normalizes both.
|
|
29
|
+
*
|
|
30
|
+
* If the caller already supplied EIP712Domain, theirs is authoritative and the
|
|
31
|
+
* input is returned untouched. Otherwise a new object is returned (input is not
|
|
32
|
+
* mutated) with EIP712Domain inserted first.
|
|
33
|
+
*
|
|
34
|
+
* @see https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
|
|
35
|
+
*/
|
|
36
|
+
export declare function withEIP712Domain(typedData: EIP712TypedData): EIP712TypedData;
|
|
2
37
|
/**
|
|
3
38
|
* Standard signature format for most blockchains (EVM, Solana, etc.)
|
|
4
39
|
*/
|
package/dist/signature.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,iCAAiC,EAClC,MAAM,aAAa,CAAA;AAEpB;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,KAAK,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,kBAAkB,CAAA;CAC5B;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,aAAa,GAAG,YAAY,CAAA;AAE5E;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,aAAa,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,WAAW,CACT,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,yBAAyB,GAAG,iCAAiC,GACtE,OAAO,CAAC,eAAe,CAAC,CAAA;CAC5B"}
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,iCAAiC,EAClC,MAAM,aAAa,CAAA;AAEpB;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;IACD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAC5D,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,eAAe,GAAG,eAAe,CAyB5E;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,KAAK,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,kBAAkB,CAAA;CAC5B;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,aAAa,GAAG,YAAY,CAAA;AAE5E;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,aAAa,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,WAAW,CACT,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,yBAAyB,GAAG,iCAAiC,GACtE,OAAO,CAAC,eAAe,CAAC,CAAA;CAC5B"}
|
package/dist/signature.js
CHANGED
|
@@ -1,2 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Ensures the EIP712Domain type entry is present in `types`, derived from the
|
|
3
|
+
* fields actually set on `domain`, in EIP-712's canonical order.
|
|
4
|
+
*
|
|
5
|
+
* Why: some wallets (notably Trust Wallet and several mobile wallets over
|
|
6
|
+
* WalletConnect) parse the `types` object strictly and reject an
|
|
7
|
+
* eth_signTypedData_v4 request that omits EIP712Domain. Desktop MetaMask derives
|
|
8
|
+
* it implicitly, so callers tend to leave it out — this normalizes both.
|
|
9
|
+
*
|
|
10
|
+
* If the caller already supplied EIP712Domain, theirs is authoritative and the
|
|
11
|
+
* input is returned untouched. Otherwise a new object is returned (input is not
|
|
12
|
+
* mutated) with EIP712Domain inserted first.
|
|
13
|
+
*
|
|
14
|
+
* @see https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
|
|
15
|
+
*/
|
|
16
|
+
export function withEIP712Domain(typedData) {
|
|
17
|
+
if (Object.hasOwn(typedData.types, 'EIP712Domain'))
|
|
18
|
+
return typedData;
|
|
19
|
+
const { domain } = typedData;
|
|
20
|
+
const domainFields = [];
|
|
21
|
+
// Canonical field order — the on-chain domainSeparator hash depends on it.
|
|
22
|
+
if (domain.name !== undefined)
|
|
23
|
+
domainFields.push({ name: 'name', type: 'string' });
|
|
24
|
+
if (domain.version !== undefined)
|
|
25
|
+
domainFields.push({ name: 'version', type: 'string' });
|
|
26
|
+
if (domain.chainId !== undefined)
|
|
27
|
+
domainFields.push({ name: 'chainId', type: 'uint256' });
|
|
28
|
+
if (domain.verifyingContract !== undefined)
|
|
29
|
+
domainFields.push({ name: 'verifyingContract', type: 'address' });
|
|
30
|
+
if (domain.salt !== undefined)
|
|
31
|
+
domainFields.push({ name: 'salt', type: 'bytes32' });
|
|
32
|
+
return {
|
|
33
|
+
...typedData,
|
|
34
|
+
types: {
|
|
35
|
+
EIP712Domain: domainFields,
|
|
36
|
+
...typedData.types
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
2
40
|
//# sourceMappingURL=signature.js.map
|
package/dist/signature.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAA0B;IACzD,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC;QAAE,OAAO,SAAS,CAAA;IAEpE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAC5B,MAAM,YAAY,GAA0C,EAAE,CAAA;IAE9D,2EAA2E;IAC3E,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAC3B,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IACrD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAC9B,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IACxD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAC9B,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;IACzD,IAAI,MAAM,CAAC,iBAAiB,KAAK,SAAS;QACxC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;IACnE,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAC3B,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;IAEtD,OAAO;QACL,GAAG,SAAS;QACZ,KAAK,EAAE;YACL,YAAY,EAAE,YAAY;YAC1B,GAAG,SAAS,CAAC,KAAK;SACnB;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solana tx wire-format helpers (pure byte ops, no @solana/web3.js).
|
|
3
|
+
* Wire format: [shortvec sigCount] [sigCount × 64-byte sigs] [message bytes].
|
|
4
|
+
* Used to confirm a wallet only ADDED a signature and didn't change the message.
|
|
5
|
+
*/
|
|
6
|
+
/** Offset where the message begins, or -1 if the tx bytes are malformed. */
|
|
7
|
+
export declare function solanaMessageOffset(serializedTx: Uint8Array): number;
|
|
8
|
+
/** V0 messages set the high bit (0x80) on the first message byte; legacy don't. */
|
|
9
|
+
export declare function isVersionedTransactionBytes(serializedTx: Uint8Array): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Throw unless `returned` keeps the byte-identical message of `sent` — signing
|
|
12
|
+
* may only add a signature. Compares the message region only (sigs differ).
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertSameMessage(sent: Uint8Array, returned: Uint8Array): void;
|
|
15
|
+
//# sourceMappingURL=solana-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana-message.d.ts","sourceRoot":"","sources":["../src/solana-message.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,UAAU,GAAG,MAAM,CAoBpE;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,UAAU,GAAG,OAAO,CAI7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,UAAU,GACnB,IAAI,CAuBN"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solana tx wire-format helpers (pure byte ops, no @solana/web3.js).
|
|
3
|
+
* Wire format: [shortvec sigCount] [sigCount × 64-byte sigs] [message bytes].
|
|
4
|
+
* Used to confirm a wallet only ADDED a signature and didn't change the message.
|
|
5
|
+
*/
|
|
6
|
+
/** Offset where the message begins, or -1 if the tx bytes are malformed. */
|
|
7
|
+
export function solanaMessageOffset(serializedTx) {
|
|
8
|
+
let offset = 0;
|
|
9
|
+
let sigCount = 0;
|
|
10
|
+
let shift = 0;
|
|
11
|
+
let terminated = false;
|
|
12
|
+
// shortvec: little-endian base-128, continuation bit 0x80.
|
|
13
|
+
while (offset < serializedTx.length) {
|
|
14
|
+
const byte = serializedTx[offset];
|
|
15
|
+
sigCount |= (byte & 0x7f) << shift;
|
|
16
|
+
offset++;
|
|
17
|
+
if ((byte & 0x80) === 0) {
|
|
18
|
+
terminated = true;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
shift += 7;
|
|
22
|
+
if (shift >= 32)
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
if (!terminated)
|
|
26
|
+
return -1;
|
|
27
|
+
const messageStart = offset + sigCount * 64;
|
|
28
|
+
return messageStart >= serializedTx.length ? -1 : messageStart;
|
|
29
|
+
}
|
|
30
|
+
/** V0 messages set the high bit (0x80) on the first message byte; legacy don't. */
|
|
31
|
+
export function isVersionedTransactionBytes(serializedTx) {
|
|
32
|
+
const messageStart = solanaMessageOffset(serializedTx);
|
|
33
|
+
if (messageStart < 0)
|
|
34
|
+
return false;
|
|
35
|
+
return (serializedTx[messageStart] & 0x80) !== 0;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Throw unless `returned` keeps the byte-identical message of `sent` — signing
|
|
39
|
+
* may only add a signature. Compares the message region only (sigs differ).
|
|
40
|
+
*/
|
|
41
|
+
export function assertSameMessage(sent, returned) {
|
|
42
|
+
const sentStart = solanaMessageOffset(sent);
|
|
43
|
+
const returnedStart = solanaMessageOffset(returned);
|
|
44
|
+
if (sentStart < 0 || returnedStart < 0) {
|
|
45
|
+
throw new Error('Malformed Solana transaction: cannot locate message bytes');
|
|
46
|
+
}
|
|
47
|
+
// Signing fills a pre-allocated slot, so the sig count (hence the offset)
|
|
48
|
+
// can't change. A different offset = added/dropped signer → reject.
|
|
49
|
+
if (sentStart !== returnedStart) {
|
|
50
|
+
throw new Error('Signed transaction does not match the request — signature layout changed');
|
|
51
|
+
}
|
|
52
|
+
const sentMsg = sent.subarray(sentStart);
|
|
53
|
+
const returnedMsg = returned.subarray(returnedStart);
|
|
54
|
+
const tampered = sentMsg.length !== returnedMsg.length ||
|
|
55
|
+
sentMsg.some((byte, i) => byte !== returnedMsg[i]);
|
|
56
|
+
if (tampered) {
|
|
57
|
+
throw new Error('Signed transaction does not match the request — wallet returned a different transaction');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=solana-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solana-message.js","sourceRoot":"","sources":["../src/solana-message.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB,CAAC,YAAwB;IAC1D,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,UAAU,GAAG,KAAK,CAAA;IACtB,2DAA2D;IAC3D,OAAO,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAE,CAAA;QAClC,QAAQ,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,CAAA;QAClC,MAAM,EAAE,CAAA;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,UAAU,GAAG,IAAI,CAAA;YACjB,MAAK;QACP,CAAC;QACD,KAAK,IAAI,CAAC,CAAA;QACV,IAAI,KAAK,IAAI,EAAE;YAAE,OAAO,CAAC,CAAC,CAAA;IAC5B,CAAC;IACD,IAAI,CAAC,UAAU;QAAE,OAAO,CAAC,CAAC,CAAA;IAC1B,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,EAAE,CAAA;IAC3C,OAAO,YAAY,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAA;AAChE,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,2BAA2B,CAAC,YAAwB;IAClE,MAAM,YAAY,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAA;IACtD,IAAI,YAAY,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAClC,OAAO,CAAC,YAAY,CAAC,YAAY,CAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAgB,EAChB,QAAoB;IAEpB,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAC3C,MAAM,aAAa,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACnD,IAAI,SAAS,GAAG,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAA;IAC9E,CAAC;IACD,0EAA0E;IAC1E,oEAAoE;IACpE,IAAI,SAAS,KAAK,aAAa,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAA;IACH,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IACxC,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAA;IACpD,MAAM,QAAQ,GACZ,OAAO,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACpD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAA;IACH,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
package/src/connector.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
TransactionRequest,
|
|
17
17
|
TransactionResult
|
|
18
18
|
} from './transactions'
|
|
19
|
-
import type { SignatureType } from './signature'
|
|
19
|
+
import type { SignatureType, EIP712TypedData } from './signature'
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Result interface for connect operations
|
|
@@ -122,6 +122,20 @@ export interface Connector {
|
|
|
122
122
|
| TonConnectWalletProvider
|
|
123
123
|
): Promise<SignatureType>
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Sign EIP-712 typed structured data (eth_signTypedData_v4).
|
|
127
|
+
* Required for ERC-3009 (Transfer With Authorization) and EIP-2612 (Permit) relay flows.
|
|
128
|
+
* EVM-only (eip155); connectors that don't support it leave this undefined.
|
|
129
|
+
* @returns A promise that resolves to the 65-byte hex signature (0x-prefixed)
|
|
130
|
+
*/
|
|
131
|
+
signTypedData?(
|
|
132
|
+
typedData: EIP712TypedData,
|
|
133
|
+
provider?:
|
|
134
|
+
| ExtensionInjectedProvider
|
|
135
|
+
| IntegratedBrowserInjectedProvider
|
|
136
|
+
| WalletConnectProvider
|
|
137
|
+
): Promise<string>
|
|
138
|
+
|
|
125
139
|
/**
|
|
126
140
|
* Send a transaction with the connected wallet
|
|
127
141
|
* @param request The transaction request parameters
|
|
@@ -141,4 +155,11 @@ export interface Connector {
|
|
|
141
155
|
from: string,
|
|
142
156
|
networks: Network[]
|
|
143
157
|
): Promise<Record<string, EVMCapabilities>>
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sign a Solana tx without broadcasting — raw bytes in/out (no @solana/web3.js
|
|
161
|
+
* for callers). Fee-payer relay flows: the wallet adds only the user's
|
|
162
|
+
* signature, the relay broadcasts. Optional; not all connectors support it.
|
|
163
|
+
*/
|
|
164
|
+
signSolanaTransactionBytes?(serializedTx: Uint8Array): Promise<Uint8Array>
|
|
144
165
|
}
|
package/src/index.ts
CHANGED
package/src/react-hooks.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
WalletError,
|
|
10
10
|
EVMCapabilities,
|
|
11
11
|
SignatureType,
|
|
12
|
+
EIP712TypedData,
|
|
12
13
|
WalletMetadata
|
|
13
14
|
} from './index'
|
|
14
15
|
|
|
@@ -258,6 +259,50 @@ export interface UseSignMessageReturn {
|
|
|
258
259
|
error: WalletError | undefined
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
// useSignTypedData hook types
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Return type for the useSignTypedData hook.
|
|
266
|
+
* Provides EIP-712 typed data signing (eth_signTypedData_v4) for EVM wallets.
|
|
267
|
+
* Used by ERC-3009 (Transfer With Authorization) and EIP-2612 (Permit) relay flows.
|
|
268
|
+
*/
|
|
269
|
+
export interface UseSignTypedDataReturn {
|
|
270
|
+
/**
|
|
271
|
+
* Signs EIP-712 typed data with the connected EVM wallet
|
|
272
|
+
* @param typedData - The EIP-712 typed data to sign
|
|
273
|
+
* @returns Promise that resolves to the 65-byte hex signature (0x-prefixed)
|
|
274
|
+
* @throws Error if no wallet is connected, the wallet is non-EVM, or signing fails
|
|
275
|
+
*/
|
|
276
|
+
signTypedData: (typedData: EIP712TypedData) => Promise<string>
|
|
277
|
+
/**
|
|
278
|
+
* Indicates if a typed-data signing operation is currently in progress
|
|
279
|
+
*/
|
|
280
|
+
isLoading: boolean
|
|
281
|
+
/**
|
|
282
|
+
* The raw hex signature from the last successful sign, undefined if not yet signed
|
|
283
|
+
*/
|
|
284
|
+
signature: string | undefined
|
|
285
|
+
/**
|
|
286
|
+
* Error from the last signing attempt, undefined if no error occurred
|
|
287
|
+
*/
|
|
288
|
+
error: WalletError | undefined
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// useSignSolanaTransaction hook types
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Return type for useSignSolanaTransaction — sign-only Solana signing (bytes in,
|
|
295
|
+
* bytes out) for fee-payer relay flows where the relay, not the wallet, broadcasts.
|
|
296
|
+
* @example
|
|
297
|
+
* const signed = await signSolanaTransaction(unsignedBytes)
|
|
298
|
+
*/
|
|
299
|
+
export interface UseSignSolanaTransactionReturn {
|
|
300
|
+
/** Sign serialized tx bytes without broadcasting. Throws if no wallet / signing fails. */
|
|
301
|
+
signSolanaTransaction: (serializedTx: Uint8Array) => Promise<Uint8Array>
|
|
302
|
+
isLoading: boolean
|
|
303
|
+
error: WalletError | undefined
|
|
304
|
+
}
|
|
305
|
+
|
|
261
306
|
// useTransaction hook types
|
|
262
307
|
|
|
263
308
|
/**
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { withEIP712Domain } from './signature'
|
|
3
|
+
import type { EIP712TypedData } from './signature'
|
|
4
|
+
|
|
5
|
+
const baseTypes = {
|
|
6
|
+
TransferWithAuthorization: [
|
|
7
|
+
{ name: 'from', type: 'address' },
|
|
8
|
+
{ name: 'to', type: 'address' },
|
|
9
|
+
{ name: 'value', type: 'uint256' },
|
|
10
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
11
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
12
|
+
{ name: 'nonce', type: 'bytes32' }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const baseMessage = {
|
|
17
|
+
from: '0xabc',
|
|
18
|
+
to: '0xdef',
|
|
19
|
+
value: '1000000',
|
|
20
|
+
validAfter: 0,
|
|
21
|
+
validBefore: 9999999999,
|
|
22
|
+
nonce: '0xdeadbeef'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('withEIP712Domain', () => {
|
|
26
|
+
it('injects EIP712Domain with all four standard fields when all are present in domain', () => {
|
|
27
|
+
const input: EIP712TypedData = {
|
|
28
|
+
domain: {
|
|
29
|
+
name: 'USD Coin',
|
|
30
|
+
version: '2',
|
|
31
|
+
chainId: 1,
|
|
32
|
+
verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
|
|
33
|
+
},
|
|
34
|
+
types: baseTypes,
|
|
35
|
+
primaryType: 'TransferWithAuthorization',
|
|
36
|
+
message: baseMessage
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = withEIP712Domain(input)
|
|
40
|
+
|
|
41
|
+
expect(result.types.EIP712Domain).toEqual([
|
|
42
|
+
{ name: 'name', type: 'string' },
|
|
43
|
+
{ name: 'version', type: 'string' },
|
|
44
|
+
{ name: 'chainId', type: 'uint256' },
|
|
45
|
+
{ name: 'verifyingContract', type: 'address' }
|
|
46
|
+
])
|
|
47
|
+
// Existing types are preserved
|
|
48
|
+
expect(result.types.TransferWithAuthorization).toEqual(
|
|
49
|
+
baseTypes.TransferWithAuthorization
|
|
50
|
+
)
|
|
51
|
+
// Other fields are unchanged
|
|
52
|
+
expect(result.domain).toBe(input.domain)
|
|
53
|
+
expect(result.primaryType).toBe(input.primaryType)
|
|
54
|
+
expect(result.message).toBe(input.message)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('omits version from EIP712Domain when domain has no version (UNI-style)', () => {
|
|
58
|
+
const input: EIP712TypedData = {
|
|
59
|
+
domain: { name: 'Uniswap V2', chainId: 1, verifyingContract: '0x...' },
|
|
60
|
+
types: { Permit: [{ name: 'owner', type: 'address' }] },
|
|
61
|
+
primaryType: 'Permit',
|
|
62
|
+
message: {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = withEIP712Domain(input)
|
|
66
|
+
|
|
67
|
+
expect(result.types.EIP712Domain).toEqual([
|
|
68
|
+
{ name: 'name', type: 'string' },
|
|
69
|
+
{ name: 'chainId', type: 'uint256' },
|
|
70
|
+
{ name: 'verifyingContract', type: 'address' }
|
|
71
|
+
])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('includes salt when present in domain', () => {
|
|
75
|
+
const input: EIP712TypedData = {
|
|
76
|
+
domain: {
|
|
77
|
+
name: 'MyToken',
|
|
78
|
+
chainId: 137,
|
|
79
|
+
salt: '0xdeadbeef00000000000000000000000000000000000000000000000000000000'
|
|
80
|
+
},
|
|
81
|
+
types: baseTypes,
|
|
82
|
+
primaryType: 'TransferWithAuthorization',
|
|
83
|
+
message: baseMessage
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = withEIP712Domain(input)
|
|
87
|
+
|
|
88
|
+
expect(result.types.EIP712Domain).toEqual([
|
|
89
|
+
{ name: 'name', type: 'string' },
|
|
90
|
+
{ name: 'chainId', type: 'uint256' },
|
|
91
|
+
{ name: 'salt', type: 'bytes32' }
|
|
92
|
+
])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('preserves canonical field order: name, version, chainId, verifyingContract, salt', () => {
|
|
96
|
+
const input: EIP712TypedData = {
|
|
97
|
+
domain: {
|
|
98
|
+
salt: '0x00000000000000000000000000000000000000000000000000000000000000ab',
|
|
99
|
+
verifyingContract: '0x123',
|
|
100
|
+
name: 'Token',
|
|
101
|
+
chainId: 1,
|
|
102
|
+
version: '1'
|
|
103
|
+
},
|
|
104
|
+
types: baseTypes,
|
|
105
|
+
primaryType: 'TransferWithAuthorization',
|
|
106
|
+
message: baseMessage
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = withEIP712Domain(input)
|
|
110
|
+
const names = result.types.EIP712Domain.map(f => f.name)
|
|
111
|
+
|
|
112
|
+
expect(names).toEqual([
|
|
113
|
+
'name',
|
|
114
|
+
'version',
|
|
115
|
+
'chainId',
|
|
116
|
+
'verifyingContract',
|
|
117
|
+
'salt'
|
|
118
|
+
])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does not modify input when EIP712Domain is already present', () => {
|
|
122
|
+
const existingEIP712Domain = [
|
|
123
|
+
{ name: 'name', type: 'string' },
|
|
124
|
+
{ name: 'chainId', type: 'uint256' }
|
|
125
|
+
]
|
|
126
|
+
const input: EIP712TypedData = {
|
|
127
|
+
domain: {
|
|
128
|
+
name: 'USD Coin',
|
|
129
|
+
version: '2',
|
|
130
|
+
chainId: 1,
|
|
131
|
+
verifyingContract: '0xabc'
|
|
132
|
+
},
|
|
133
|
+
types: { EIP712Domain: existingEIP712Domain, ...baseTypes },
|
|
134
|
+
primaryType: 'TransferWithAuthorization',
|
|
135
|
+
message: baseMessage
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = withEIP712Domain(input)
|
|
139
|
+
|
|
140
|
+
// Returns input unchanged — caller's EIP712Domain is authoritative
|
|
141
|
+
expect(result).toBe(input)
|
|
142
|
+
expect(result.types.EIP712Domain).toBe(existingEIP712Domain)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('returns a new object (does not mutate input) when injecting EIP712Domain', () => {
|
|
146
|
+
const input: EIP712TypedData = {
|
|
147
|
+
domain: {
|
|
148
|
+
name: 'USD Coin',
|
|
149
|
+
version: '2',
|
|
150
|
+
chainId: 1,
|
|
151
|
+
verifyingContract: '0xabc'
|
|
152
|
+
},
|
|
153
|
+
types: { ...baseTypes },
|
|
154
|
+
primaryType: 'TransferWithAuthorization',
|
|
155
|
+
message: baseMessage
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = withEIP712Domain(input)
|
|
159
|
+
|
|
160
|
+
expect(result).not.toBe(input)
|
|
161
|
+
expect(result.types).not.toBe(input.types)
|
|
162
|
+
// Original is untouched
|
|
163
|
+
expect(input.types.EIP712Domain).toBeUndefined()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('handles a domain with only chainId (minimal domain)', () => {
|
|
167
|
+
const input: EIP712TypedData = {
|
|
168
|
+
domain: { chainId: 8453 },
|
|
169
|
+
types: baseTypes,
|
|
170
|
+
primaryType: 'TransferWithAuthorization',
|
|
171
|
+
message: baseMessage
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const result = withEIP712Domain(input)
|
|
175
|
+
|
|
176
|
+
expect(result.types.EIP712Domain).toEqual([
|
|
177
|
+
{ name: 'chainId', type: 'uint256' }
|
|
178
|
+
])
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('places EIP712Domain before other types in the types object', () => {
|
|
182
|
+
const input: EIP712TypedData = {
|
|
183
|
+
domain: {
|
|
184
|
+
name: 'USD Coin',
|
|
185
|
+
version: '2',
|
|
186
|
+
chainId: 1,
|
|
187
|
+
verifyingContract: '0xabc'
|
|
188
|
+
},
|
|
189
|
+
types: baseTypes,
|
|
190
|
+
primaryType: 'TransferWithAuthorization',
|
|
191
|
+
message: baseMessage
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const keys = Object.keys(withEIP712Domain(input).types)
|
|
195
|
+
expect(keys[0]).toBe('EIP712Domain')
|
|
196
|
+
})
|
|
197
|
+
})
|
package/src/signature.ts
CHANGED
|
@@ -3,6 +3,65 @@ import type {
|
|
|
3
3
|
IntegratedBrowserInjectedProvider
|
|
4
4
|
} from './UWC-state'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* EIP-712 typed structured data for eth_signTypedData_v4.
|
|
8
|
+
* Used by ERC-3009 (Transfer With Authorization), EIP-2612 (Permit), and similar standards.
|
|
9
|
+
*/
|
|
10
|
+
export interface EIP712TypedData {
|
|
11
|
+
domain: {
|
|
12
|
+
name?: string
|
|
13
|
+
version?: string
|
|
14
|
+
chainId?: number
|
|
15
|
+
verifyingContract?: string
|
|
16
|
+
salt?: string
|
|
17
|
+
}
|
|
18
|
+
types: Record<string, Array<{ name: string; type: string }>>
|
|
19
|
+
primaryType: string
|
|
20
|
+
message: Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Ensures the EIP712Domain type entry is present in `types`, derived from the
|
|
25
|
+
* fields actually set on `domain`, in EIP-712's canonical order.
|
|
26
|
+
*
|
|
27
|
+
* Why: some wallets (notably Trust Wallet and several mobile wallets over
|
|
28
|
+
* WalletConnect) parse the `types` object strictly and reject an
|
|
29
|
+
* eth_signTypedData_v4 request that omits EIP712Domain. Desktop MetaMask derives
|
|
30
|
+
* it implicitly, so callers tend to leave it out — this normalizes both.
|
|
31
|
+
*
|
|
32
|
+
* If the caller already supplied EIP712Domain, theirs is authoritative and the
|
|
33
|
+
* input is returned untouched. Otherwise a new object is returned (input is not
|
|
34
|
+
* mutated) with EIP712Domain inserted first.
|
|
35
|
+
*
|
|
36
|
+
* @see https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
|
|
37
|
+
*/
|
|
38
|
+
export function withEIP712Domain(typedData: EIP712TypedData): EIP712TypedData {
|
|
39
|
+
if (Object.hasOwn(typedData.types, 'EIP712Domain')) return typedData
|
|
40
|
+
|
|
41
|
+
const { domain } = typedData
|
|
42
|
+
const domainFields: Array<{ name: string; type: string }> = []
|
|
43
|
+
|
|
44
|
+
// Canonical field order — the on-chain domainSeparator hash depends on it.
|
|
45
|
+
if (domain.name !== undefined)
|
|
46
|
+
domainFields.push({ name: 'name', type: 'string' })
|
|
47
|
+
if (domain.version !== undefined)
|
|
48
|
+
domainFields.push({ name: 'version', type: 'string' })
|
|
49
|
+
if (domain.chainId !== undefined)
|
|
50
|
+
domainFields.push({ name: 'chainId', type: 'uint256' })
|
|
51
|
+
if (domain.verifyingContract !== undefined)
|
|
52
|
+
domainFields.push({ name: 'verifyingContract', type: 'address' })
|
|
53
|
+
if (domain.salt !== undefined)
|
|
54
|
+
domainFields.push({ name: 'salt', type: 'bytes32' })
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...typedData,
|
|
58
|
+
types: {
|
|
59
|
+
EIP712Domain: domainFields,
|
|
60
|
+
...typedData.types
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
6
65
|
/**
|
|
7
66
|
* Standard signature format for most blockchains (EVM, Solana, etc.)
|
|
8
67
|
*/
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
solanaMessageOffset,
|
|
4
|
+
isVersionedTransactionBytes,
|
|
5
|
+
assertSameMessage
|
|
6
|
+
} from './solana-message'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build a fake serialized tx: [sigCount (single shortvec byte, <128)]
|
|
10
|
+
* + sigCount × 64 signature bytes + message bytes.
|
|
11
|
+
*/
|
|
12
|
+
function makeTx(
|
|
13
|
+
sigCount: number,
|
|
14
|
+
message: number[],
|
|
15
|
+
sigFill: (slot: number) => number[] = () => new Array(64).fill(0)
|
|
16
|
+
): Uint8Array {
|
|
17
|
+
const sigs: number[] = []
|
|
18
|
+
for (let s = 0; s < sigCount; s++) sigs.push(...sigFill(s))
|
|
19
|
+
return Uint8Array.from([sigCount, ...sigs, ...message])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('solanaMessageOffset', () => {
|
|
23
|
+
it('points just past the signature array', () => {
|
|
24
|
+
expect(solanaMessageOffset(makeTx(1, [0x01, 0x02]))).toBe(1 + 64)
|
|
25
|
+
expect(solanaMessageOffset(makeTx(2, [0x01]))).toBe(1 + 128)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns -1 when the shortvec never terminates', () => {
|
|
29
|
+
expect(solanaMessageOffset(Uint8Array.from([0x80, 0x80, 0x80]))).toBe(-1)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('returns -1 when the signatures overrun the buffer', () => {
|
|
33
|
+
// Claims 2 sigs (128 bytes) but only 10 bytes follow.
|
|
34
|
+
expect(
|
|
35
|
+
solanaMessageOffset(Uint8Array.from([0x02, ...new Array(10).fill(0)]))
|
|
36
|
+
).toBe(-1)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('returns -1 when there is no message after the signatures', () => {
|
|
40
|
+
// 1 sig (64 bytes), nothing after → offset lands exactly at the end.
|
|
41
|
+
expect(
|
|
42
|
+
solanaMessageOffset(Uint8Array.from([0x01, ...new Array(64).fill(0)]))
|
|
43
|
+
).toBe(-1)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('isVersionedTransactionBytes', () => {
|
|
48
|
+
it('true when the first message byte sets the version high bit', () => {
|
|
49
|
+
expect(isVersionedTransactionBytes(makeTx(1, [0x80, 0x00]))).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('false for a legacy message', () => {
|
|
53
|
+
expect(isVersionedTransactionBytes(makeTx(1, [0x01, 0x00]))).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('false for malformed bytes', () => {
|
|
57
|
+
expect(isVersionedTransactionBytes(Uint8Array.from([0x80, 0x80]))).toBe(
|
|
58
|
+
false
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('assertSameMessage', () => {
|
|
64
|
+
const message = [0x09, 0x08, 0x07, 0x06]
|
|
65
|
+
|
|
66
|
+
it('passes when only the signature differs (freshly signed tx)', () => {
|
|
67
|
+
// Relay fee-payer in slot 0 (unchanged), user signs slot 1.
|
|
68
|
+
const sent = makeTx(2, message, () => new Array(64).fill(0))
|
|
69
|
+
const signed = makeTx(2, message, slot =>
|
|
70
|
+
new Array(64).fill(slot === 1 ? 0xff : 0)
|
|
71
|
+
)
|
|
72
|
+
expect(() => assertSameMessage(sent, signed)).not.toThrow()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('throws when the signature count changes, even if message bytes match', () => {
|
|
76
|
+
// returned drops a signature slot (2 → 1) but keeps the same message bytes;
|
|
77
|
+
// the offset shifts, so a naive message-only compare would wrongly pass.
|
|
78
|
+
const sent = makeTx(2, message)
|
|
79
|
+
const returnedFewerSlots = makeTx(1, message)
|
|
80
|
+
expect(() => assertSameMessage(sent, returnedFewerSlots)).toThrow(
|
|
81
|
+
'signature layout changed'
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('throws when message bytes change (tampered recipient/amount)', () => {
|
|
86
|
+
const sent = makeTx(1, message)
|
|
87
|
+
const tampered = makeTx(1, [0x09, 0x08, 0x07, 0x05]) // last byte flipped
|
|
88
|
+
expect(() => assertSameMessage(sent, tampered)).toThrow(
|
|
89
|
+
'Signed transaction does not match the request'
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('throws when the message length changes', () => {
|
|
94
|
+
const sent = makeTx(1, message)
|
|
95
|
+
const longer = makeTx(1, [...message, 0x00])
|
|
96
|
+
expect(() => assertSameMessage(sent, longer)).toThrow(
|
|
97
|
+
'Signed transaction does not match the request'
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('throws on malformed input (cannot locate message)', () => {
|
|
102
|
+
const sent = makeTx(1, message)
|
|
103
|
+
const malformed = Uint8Array.from([0x80, 0x80])
|
|
104
|
+
expect(() => assertSameMessage(sent, malformed)).toThrow(
|
|
105
|
+
'Malformed Solana transaction'
|
|
106
|
+
)
|
|
107
|
+
expect(() => assertSameMessage(malformed, sent)).toThrow(
|
|
108
|
+
'Malformed Solana transaction'
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solana tx wire-format helpers (pure byte ops, no @solana/web3.js).
|
|
3
|
+
* Wire format: [shortvec sigCount] [sigCount × 64-byte sigs] [message bytes].
|
|
4
|
+
* Used to confirm a wallet only ADDED a signature and didn't change the message.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Offset where the message begins, or -1 if the tx bytes are malformed. */
|
|
8
|
+
export function solanaMessageOffset(serializedTx: Uint8Array): number {
|
|
9
|
+
let offset = 0
|
|
10
|
+
let sigCount = 0
|
|
11
|
+
let shift = 0
|
|
12
|
+
let terminated = false
|
|
13
|
+
// shortvec: little-endian base-128, continuation bit 0x80.
|
|
14
|
+
while (offset < serializedTx.length) {
|
|
15
|
+
const byte = serializedTx[offset]!
|
|
16
|
+
sigCount |= (byte & 0x7f) << shift
|
|
17
|
+
offset++
|
|
18
|
+
if ((byte & 0x80) === 0) {
|
|
19
|
+
terminated = true
|
|
20
|
+
break
|
|
21
|
+
}
|
|
22
|
+
shift += 7
|
|
23
|
+
if (shift >= 32) return -1
|
|
24
|
+
}
|
|
25
|
+
if (!terminated) return -1
|
|
26
|
+
const messageStart = offset + sigCount * 64
|
|
27
|
+
return messageStart >= serializedTx.length ? -1 : messageStart
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** V0 messages set the high bit (0x80) on the first message byte; legacy don't. */
|
|
31
|
+
export function isVersionedTransactionBytes(serializedTx: Uint8Array): boolean {
|
|
32
|
+
const messageStart = solanaMessageOffset(serializedTx)
|
|
33
|
+
if (messageStart < 0) return false
|
|
34
|
+
return (serializedTx[messageStart]! & 0x80) !== 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Throw unless `returned` keeps the byte-identical message of `sent` — signing
|
|
39
|
+
* may only add a signature. Compares the message region only (sigs differ).
|
|
40
|
+
*/
|
|
41
|
+
export function assertSameMessage(
|
|
42
|
+
sent: Uint8Array,
|
|
43
|
+
returned: Uint8Array
|
|
44
|
+
): void {
|
|
45
|
+
const sentStart = solanaMessageOffset(sent)
|
|
46
|
+
const returnedStart = solanaMessageOffset(returned)
|
|
47
|
+
if (sentStart < 0 || returnedStart < 0) {
|
|
48
|
+
throw new Error('Malformed Solana transaction: cannot locate message bytes')
|
|
49
|
+
}
|
|
50
|
+
// Signing fills a pre-allocated slot, so the sig count (hence the offset)
|
|
51
|
+
// can't change. A different offset = added/dropped signer → reject.
|
|
52
|
+
if (sentStart !== returnedStart) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'Signed transaction does not match the request — signature layout changed'
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
const sentMsg = sent.subarray(sentStart)
|
|
58
|
+
const returnedMsg = returned.subarray(returnedStart)
|
|
59
|
+
const tampered =
|
|
60
|
+
sentMsg.length !== returnedMsg.length ||
|
|
61
|
+
sentMsg.some((byte, i) => byte !== returnedMsg[i])
|
|
62
|
+
if (tampered) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'Signed transaction does not match the request — wallet returned a different transaction'
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|