@authon/react-native-wallet 0.1.0 → 0.4.0

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +34 -4
  3. package/android/src/main/AndroidManifest.xml +4 -0
  4. package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +57 -3
  5. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AndroidCapabilityProbe.kt +86 -0
  6. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +29 -2
  7. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bitcoin.kt +296 -0
  8. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Chains.kt +157 -0
  9. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Der.kt +17 -0
  10. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Encodings.kt +133 -0
  11. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +6 -2
  12. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +27 -23
  13. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Solana.kt +195 -0
  14. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Tron.kt +184 -0
  15. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +16 -8
  16. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt +91 -0
  17. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +18 -2
  18. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +103 -1
  19. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +8 -1
  20. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Xrp.kt +193 -0
  21. package/build/client.d.ts +18 -2
  22. package/build/client.d.ts.map +1 -1
  23. package/build/client.js +88 -6
  24. package/build/client.js.map +1 -1
  25. package/build/errors.d.ts +8 -1
  26. package/build/errors.d.ts.map +1 -1
  27. package/build/errors.js +7 -1
  28. package/build/errors.js.map +1 -1
  29. package/build/index.d.ts +1 -1
  30. package/build/index.d.ts.map +1 -1
  31. package/build/native.d.ts +5 -1
  32. package/build/native.d.ts.map +1 -1
  33. package/build/native.js.map +1 -1
  34. package/build/types.d.ts +47 -1
  35. package/build/types.d.ts.map +1 -1
  36. package/ios/AuthonReactNativeWallet.podspec +6 -2
  37. package/ios/AuthonWalletModule.swift +46 -3
  38. package/ios/vendor/AuthonWallet/AuthonWallet.swift +24 -4
  39. package/ios/vendor/AuthonWallet/Capabilities.swift +149 -0
  40. package/ios/vendor/AuthonWallet/Chains/Bitcoin.swift +296 -0
  41. package/ios/vendor/AuthonWallet/Chains/Solana.swift +207 -0
  42. package/ios/vendor/AuthonWallet/Chains/Tron.swift +181 -0
  43. package/ios/vendor/AuthonWallet/Chains/XRP.swift +191 -0
  44. package/ios/vendor/AuthonWallet/Crypto/Chains.swift +278 -0
  45. package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +24 -0
  46. package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +19 -8
  47. package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +101 -2
  48. package/ios/vendor/AuthonWallet/WalletError.swift +32 -2
  49. package/ios/vendor/CKeccak/README.md +6 -3
  50. package/ios/vendor/CKeccak/include/ckeccak.h +4 -0
  51. package/ios/vendor/CKeccak/ripemd160.c +343 -0
  52. package/ios/vendor/CKeccak/ripemd160.h +22 -0
  53. package/ios/vendor/CMonocypher/LICENCE.md +173 -0
  54. package/ios/vendor/CMonocypher/README.md +27 -0
  55. package/ios/vendor/CMonocypher/include/monocypher-ed25519.h +140 -0
  56. package/ios/vendor/CMonocypher/include/monocypher.h +321 -0
  57. package/ios/vendor/CMonocypher/monocypher-ed25519.c +500 -0
  58. package/ios/vendor/CMonocypher/monocypher.c +2956 -0
  59. package/ios/vendor/GENERATED.md +1 -1
  60. package/package.json +3 -2
  61. package/src/client.ts +90 -6
  62. package/src/errors.ts +7 -1
  63. package/src/index.ts +5 -0
  64. package/src/native.ts +4 -2
  65. package/src/types.ts +52 -1
@@ -1,4 +1,4 @@
1
1
  # Generated sources
2
2
 
3
- Everything in this folder is copied from `swift/Sources/{AuthonWallet,CSecp256k1,CKeccak}` by
3
+ Everything in this folder is copied from `swift/Sources/{AuthonWallet,CSecp256k1,CKeccak,CMonocypher}` by
4
4
  `scripts/sync-native-wallet.mjs` (run by the package build). Do not edit these files; change the native SDK sources and run the script. `--check` fails on any difference.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authon/react-native-wallet",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
4
  "description": "Authon embedded wallet for React Native (Expo native module wrapping the Swift and Kotlin wallet SDKs)",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -21,7 +21,8 @@
21
21
  "android/consumer-rules.pro",
22
22
  "android/src",
23
23
  "expo-module.config.json",
24
- "README.md"
24
+ "README.md",
25
+ "CHANGELOG.md"
25
26
  ],
26
27
  "license": "MIT",
27
28
  "repository": {
package/src/client.ts CHANGED
@@ -9,7 +9,12 @@ import type {
9
9
  Hex,
10
10
  TypedDataPayload,
11
11
  WalletAccount,
12
+ CapabilityCheck,
13
+ CapabilityStatus,
12
14
  WalletAppConfig,
15
+ WalletCapabilities,
16
+ WalletChain,
17
+ WalletChainSignRequest,
13
18
  WalletChangeListener,
14
19
  WalletMessage,
15
20
  WalletSignOptions,
@@ -43,9 +48,16 @@ function toWallet(value: unknown): EmbeddedWallet {
43
48
  };
44
49
  }
45
50
 
51
+ const CHAINS: WalletChain[] = ['evm', 'tron', 'xrp', 'bitcoin', 'bitcoin-testnet', 'solana'];
52
+
46
53
  function toAccounts(value: unknown): WalletAccount[] {
47
54
  if (!Array.isArray(value)) throw new AuthonWalletError('UNKNOWN', 'Wallet returned no accounts');
48
- return value.filter(isObject).map((a) => ({ index: Number(a.index), path: String(a.path), address: String(a.address) }));
55
+ return value.filter(isObject).map((a) => {
56
+ const account: WalletAccount = { index: Number(a.index), path: String(a.path), address: String(a.address) };
57
+ if (CHAINS.includes(a.chain as WalletChain)) account.chain = a.chain as WalletChain;
58
+ if (typeof a.publicKey === 'string' && /^(0x)?[0-9a-fA-F]+$/.test(a.publicKey)) account.publicKey = a.publicKey;
59
+ return account;
60
+ });
49
61
  }
50
62
 
51
63
  function toHex(value: unknown): Hex {
@@ -67,6 +79,28 @@ function toAppConfig(value: unknown): WalletAppConfig {
67
79
  };
68
80
  }
69
81
 
82
+ const STATUSES: CapabilityStatus[] = ['available', 'unavailable', 'unknown'];
83
+
84
+ function toCheck(value: unknown): CapabilityCheck {
85
+ const v = isObject(value) ? value : {};
86
+ const status = STATUSES.includes(v.status as CapabilityStatus) ? (v.status as CapabilityStatus) : 'unknown';
87
+ return { status, reason: status === 'available' ? null : str(v.reason) ?? 'CHECK_FAILED' };
88
+ }
89
+
90
+ function toCapabilities(value: unknown): WalletCapabilities {
91
+ if (!isObject(value)) throw new AuthonWalletError('UNKNOWN', 'Wallet returned no capabilities');
92
+ const checks = {
93
+ app: toCheck(value.app),
94
+ wallet: toCheck(value.wallet),
95
+ secureStorage: toCheck(value.secureStorage),
96
+ deviceLock: toCheck(value.deviceLock),
97
+ passkeyBackup: toCheck(value.passkeyBackup),
98
+ };
99
+ const all = Object.values(checks).map((c) => c.status);
100
+ const ready: CapabilityStatus = all.includes('unavailable') ? 'unavailable' : all.includes('unknown') ? 'unknown' : 'available';
101
+ return { ready, ...checks, diagnosticId: str(value.diagnosticId) };
102
+ }
103
+
70
104
  /** bigint → 0x hex (transactions, like @authon/js). */
71
105
  const txReplacer = (_key: string, v: unknown) => (typeof v === 'bigint' ? `0x${v.toString(16)}` : v);
72
106
  /** bigint → decimal string (EIP-712 integer values). */
@@ -99,6 +133,17 @@ export class AuthonWalletClient {
99
133
  return toAppConfig(this.unwrap(await native.appConfig(this.id), generation));
100
134
  }
101
135
 
136
+ /**
137
+ * Whether this device and app can use the wallet, before sign-in (no session, creates nothing,
138
+ * opens no UI). `ready === 'unavailable'`: tell the user before signing in; `unknown`: continue
139
+ * but expect that feature to fail. Answers for release builds (test options are ignored).
140
+ */
141
+ async capabilities(): Promise<WalletCapabilities> {
142
+ const generation = this.generation;
143
+ const native = await this.native();
144
+ return toCapabilities(this.unwrap(await native.capabilities(this.id), generation));
145
+ }
146
+
102
147
  /** The signed-in user's wallet, or null when there is none yet. */
103
148
  async get(): Promise<EmbeddedWallet | null> {
104
149
  try {
@@ -138,15 +183,27 @@ export class AuthonWalletClient {
138
183
  return (await this.get())?.protected === true;
139
184
  }
140
185
 
141
- /** Addresses of accounts `start`..`start + count - 1` (public, no approval). */
142
- async getAccounts(options: { start?: number; count?: number } = {}): Promise<WalletAccount[]> {
186
+ /**
187
+ * Addresses of accounts `start`..`start + count - 1` (public, no approval) on `chain` (default
188
+ * `evm`). Signing is EVM only for now; other chains are for receiving and display.
189
+ */
190
+ async getAccounts(options: { start?: number; count?: number; chain?: WalletChain } = {}): Promise<WalletAccount[]> {
143
191
  const start = options.start ?? 0;
144
192
  const count = options.count ?? 1;
193
+ const chain = options.chain;
145
194
  if (!Number.isInteger(start) || start < 0 || start > MAX_ACCOUNT_INDEX || !Number.isInteger(count) || count < 1) {
146
195
  throw new AuthonWalletError('INVALID_PARAMS', 'start must be an integer 0..2147483647 and count a positive integer');
147
196
  }
148
- const list = toAccounts(await this.call((n, id, token) => n.accounts(id, token, start, count)));
149
- for (const account of list) this.accountAddresses.set(account.index, account.address);
197
+ if (chain !== undefined && !CHAINS.includes(chain)) {
198
+ throw new AuthonWalletError('INVALID_PARAMS', `chain must be one of ${CHAINS.join(', ')}`);
199
+ }
200
+ const list = toAccounts(await this.call((n, id, token) => n.accounts(id, token, start, count, chain ?? 'evm')));
201
+ if (chain && list.some((account) => account.chain !== chain)) {
202
+ // A native wallet without multichain support would answer with EVM addresses.
203
+ throw new AuthonWalletError('UNSUPPORTED_TYPE', 'This wallet build does not support other chains yet');
204
+ }
205
+ // Only EVM addresses can name a signing account.
206
+ if (!chain || chain === 'evm') for (const account of list) this.accountAddresses.set(account.index, account.address);
150
207
  return list;
151
208
  }
152
209
 
@@ -186,6 +243,30 @@ export class AuthonWalletClient {
186
243
  return toHex(await this.call((n, id, token) => n.signTransaction(id, token, json, account, address)));
187
244
  }
188
245
 
246
+ /**
247
+ * Signs an app-built transaction on another chain (NATIVE.md section 12): Solana `{ signature }`,
248
+ * TRON `{ signature, txID }`, Bitcoin `{ psbt, txHex? }`, XRP `{ signature }`. The native approval
249
+ * UI shows what the transaction does (unknown programs / contracts are shown as high risk).
250
+ */
251
+ async signChainTransaction(request: WalletChainSignRequest): Promise<Record<string, string>> {
252
+ if (!isObject(request) || (request.chain as WalletChain) === 'evm') {
253
+ throw new AuthonWalletError('INVALID_PARAMS', 'use signTransaction for EVM');
254
+ }
255
+ if (!CHAINS.includes(request.chain)) throw new AuthonWalletError('INVALID_PARAMS', `chain must be one of ${CHAINS.filter((c) => c !== 'evm').join(', ')}`);
256
+ const account = request.account ?? 0;
257
+ if (!Number.isInteger(account) || account < 0 || account > MAX_ACCOUNT_INDEX || typeof request.address !== 'string' || !request.address) {
258
+ throw new AuthonWalletError('INVALID_PARAMS', 'account index and its address are required');
259
+ }
260
+ if (!isObject(request.payload)) throw new AuthonWalletError('INVALID_PARAMS', 'payload must be an object');
261
+ const payload = JSON.stringify(request.payload, txReplacer);
262
+ const result = await this.call((n, id, token) => n.signChainTransaction(id, token, request.chain, account, request.address, payload));
263
+ if (!isObject(result)) throw new AuthonWalletError('UNKNOWN', 'Wallet returned no signature');
264
+ // Signatures and signed transactions only; anything that is not a string is dropped.
265
+ const out: Record<string, string> = {};
266
+ for (const [k, v] of Object.entries(result)) if (typeof v === 'string') out[k] = v;
267
+ return out;
268
+ }
269
+
189
270
  /** Creates the passkey backup (iOS 18+ / Android with PRF support). Needs an approved app registration. */
190
271
  async protect(): Promise<void> {
191
272
  await this.call((n, id, token) => n.protect(id, token));
@@ -329,7 +410,10 @@ export class AuthonWalletClient {
329
410
  if (!isObject(result)) throw new AuthonWalletError('UNKNOWN', 'Wallet returned no result');
330
411
  if (result.ok === true) return result.value as T;
331
412
  const code = typeof result.code === 'string' && result.code ? result.code : 'UNKNOWN';
332
- throw new AuthonWalletError(code, typeof result.message === 'string' && result.message ? result.message : code);
413
+ throw new AuthonWalletError(code, typeof result.message === 'string' && result.message ? result.message : code, {
414
+ diagnosticId: typeof result.diagnosticId === 'string' && result.diagnosticId ? result.diagnosticId : null,
415
+ retryable: result.retryable === true,
416
+ });
333
417
  }
334
418
 
335
419
  /** For accounts other than 0 the native wallet needs the expected address and refuses on mismatch. */
package/src/errors.ts CHANGED
@@ -44,9 +44,15 @@ export type WalletErrorCode =
44
44
 
45
45
  export class AuthonWalletError extends Error {
46
46
  readonly code: WalletErrorCode;
47
- constructor(code: WalletErrorCode, message?: string) {
47
+ /** The server's request ID when the failure came from an API response; log this, never secrets. */
48
+ readonly diagnosticId: string | null;
49
+ /** Whether trying the same call again can succeed (from the native wallet's code table). */
50
+ readonly retryable: boolean;
51
+ constructor(code: WalletErrorCode, message?: string, options: { diagnosticId?: string | null; retryable?: boolean } = {}) {
48
52
  super(message ?? code);
49
53
  this.name = 'AuthonWalletError';
50
54
  this.code = code;
55
+ this.diagnosticId = options.diagnosticId ?? null;
56
+ this.retryable = options.retryable ?? false;
51
57
  }
52
58
  }
package/src/index.ts CHANGED
@@ -5,6 +5,11 @@ export { useAuthonWallet } from './useAuthonWallet';
5
5
  export type { UseAuthonWalletOptions, UseAuthonWalletResult } from './useAuthonWallet';
6
6
  export type {
7
7
  AuthonWalletOptions,
8
+ CapabilityCheck,
9
+ CapabilityStatus,
10
+ WalletCapabilities,
11
+ WalletChain,
12
+ WalletChainSignRequest,
8
13
  EIP1193Provider,
9
14
  EIP1193RequestArguments,
10
15
  EmbeddedWallet,
package/src/native.ts CHANGED
@@ -3,7 +3,7 @@ import { requireOptionalNativeModule } from 'expo-modules-core';
3
3
  /** Every native function resolves an envelope instead of rejecting. */
4
4
  export type NativeResult<T> =
5
5
  | { ok: true; value: T }
6
- | { ok: false; code: string; message?: string };
6
+ | { ok: false; code: string; message?: string; diagnosticId?: string | null; retryable?: boolean };
7
7
 
8
8
  export interface NativeConfig {
9
9
  publishableKey: string;
@@ -18,9 +18,10 @@ export interface AuthonWalletNativeModule {
18
18
  configure(id: string, config: NativeConfig): Promise<NativeResult<null>>;
19
19
  release(id: string): Promise<NativeResult<null>>;
20
20
  appConfig(id: string): Promise<NativeResult<unknown>>;
21
+ capabilities(id: string): Promise<NativeResult<unknown>>;
21
22
  status(id: string, token: string): Promise<NativeResult<unknown>>;
22
23
  create(id: string, token: string): Promise<NativeResult<unknown>>;
23
- accounts(id: string, token: string, start: number, count: number): Promise<NativeResult<unknown>>;
24
+ accounts(id: string, token: string, start: number, count: number, chain: string): Promise<NativeResult<unknown>>;
24
25
  signMessage(
25
26
  id: string,
26
27
  token: string,
@@ -31,6 +32,7 @@ export interface AuthonWalletNativeModule {
31
32
  ): Promise<NativeResult<unknown>>;
32
33
  signTypedData(id: string, token: string, typedDataJson: string, account: number, address: string | null): Promise<NativeResult<unknown>>;
33
34
  signTransaction(id: string, token: string, transactionJson: string, account: number, address: string | null): Promise<NativeResult<unknown>>;
35
+ signChainTransaction(id: string, token: string, chain: string, account: number, address: string, payloadJson: string): Promise<NativeResult<unknown>>;
34
36
  protect(id: string, token: string): Promise<NativeResult<unknown>>;
35
37
  upgradeBackup(id: string, token: string): Promise<NativeResult<unknown>>;
36
38
  restore(id: string, token: string, reset: boolean): Promise<NativeResult<unknown>>;
package/src/types.ts CHANGED
@@ -27,11 +27,36 @@ export interface EmbeddedWallet {
27
27
  devices: EmbeddedWalletDevice[];
28
28
  }
29
29
 
30
- /** One HD account, derived at `m/44'/60'/0'/0/{index}`. Account 0 is `EmbeddedWallet.address`. */
30
+ /** Chains whose account addresses the wallet derives from the same seed (NATIVE.md section 11). */
31
+ export type WalletChain = 'evm' | 'tron' | 'xrp' | 'bitcoin' | 'bitcoin-testnet' | 'solana';
32
+
33
+ /**
34
+ * One HD account. EVM: `m/44'/60'/0'/0/{index}` (account 0 is `EmbeddedWallet.address`); other chains
35
+ * use their standard paths (TRON `m/44'/195'/n'/0/0`, XRP `m/44'/144'/n'/0/0`, Bitcoin
36
+ * `m/84'/0'/n'/0/0`, Bitcoin testnet `m/84'/1'/n'/0/0`, Solana `m/44'/501'/n'/0'`).
37
+ */
31
38
  export interface WalletAccount {
32
39
  index: number;
33
40
  path: string;
34
41
  address: string;
42
+ /** Set when `getAccounts` was asked for a chain. */
43
+ chain?: WalletChain;
44
+ /** Public key (hex) when the chain needs it to build a transaction (XRP SigningPubKey). */
45
+ publicKey?: string;
46
+ }
47
+
48
+ /** `signChainTransaction` input (NATIVE.md section 12). The app builds the unsigned transaction. */
49
+ export interface WalletChainSignRequest {
50
+ chain: Exclude<WalletChain, 'evm'>;
51
+ /** Account index (default 0). */
52
+ account?: number;
53
+ /** The account's address on `chain`, from getAccounts. Signing fails (ADDRESS_MISMATCH) if it differs. */
54
+ address: string;
55
+ /**
56
+ * Chain-specific unsigned transaction: Solana `{ message }` (base64 Message, legacy or v0),
57
+ * TRON `{ rawDataHex }`, Bitcoin `{ psbt }` (base64), XRP `{ txBlobHex }` (encodeForSigning output).
58
+ */
59
+ payload: Record<string, unknown>;
35
60
  }
36
61
 
37
62
  /** Signs with another account of the same wallet. Omitted or 0 = the wallet address. */
@@ -83,6 +108,32 @@ export interface WalletAppConfig {
83
108
  minFrameProtocol: number | null;
84
109
  }
85
110
 
111
+ export type CapabilityStatus = 'available' | 'unavailable' | 'unknown';
112
+
113
+ export interface CapabilityCheck {
114
+ status: CapabilityStatus;
115
+ /** Reason code (see the README table); null when available. */
116
+ reason: string | null;
117
+ }
118
+
119
+ /** `capabilities()`: can this device and app use the wallet? Asked before sign-in, no session. */
120
+ export interface WalletCapabilities {
121
+ /** unavailable if any check is unavailable, else unknown if any is unknown, else available. */
122
+ ready: CapabilityStatus;
123
+ /** App registration and approval (APP_NOT_REGISTERED, APP_SIGNATURE_MISMATCH, APP_NOT_APPROVED, NETWORK_ERROR, API_ERROR). */
124
+ app: CapabilityCheck;
125
+ /** Embedded wallet enabled for the project (WALLET_DISABLED, NETWORK_ERROR). */
126
+ wallet: CapabilityCheck;
127
+ /** Hardware-protected key storage (HARDWARE_KEYSTORE_UNAVAILABLE, SIMULATOR, CHECK_FAILED). */
128
+ secureStorage: CapabilityCheck;
129
+ /** A device passcode / biometrics is set (DEVICE_LOCK_NOT_SET, CHECK_FAILED). */
130
+ deviceLock: CapabilityCheck;
131
+ /** Passkey PRF backup, static OS/API support only (OS_UNSUPPORTED, PASSKEY_API_UNAVAILABLE, PRF_PROVIDER_UNVERIFIED). */
132
+ passkeyBackup: CapabilityCheck;
133
+ /** X-Request-Id of the app-config response. */
134
+ diagnosticId: string | null;
135
+ }
136
+
86
137
  export interface EIP1193RequestArguments {
87
138
  method: string;
88
139
  params?: unknown[] | Record<string, unknown>;