@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.
@@ -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
@@ -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;AAEhD;;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,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;CAC5C"}
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
@@ -10,4 +10,5 @@ export * from './react-hooks';
10
10
  export * from './signature';
11
11
  export * from './transactions';
12
12
  export * from './errors';
13
+ export * from './solana-message';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -10,4 +10,5 @@ export * from './react-hooks';
10
10
  export * from './signature';
11
11
  export * from './transactions';
12
12
  export * from './errors';
13
+ export * from './solana-message';
13
14
  //# sourceMappingURL=index.js.map
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"}
@@ -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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"}
@@ -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
  */
@@ -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
- export {};
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshconnect/uwc-types",
3
- "version": "0.13.0",
3
+ "version": "0.14.0-snapshot.2c887a2",
4
4
  "description": "TypeScript types for Universal Wallet Connector",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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
@@ -10,3 +10,4 @@ export * from './react-hooks'
10
10
  export * from './signature'
11
11
  export * from './transactions'
12
12
  export * from './errors'
13
+ export * from './solana-message'
@@ -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
+ }