@joeywallet/wallet-sdk 0.2.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +705 -0
  3. package/dist/client.d.ts +55 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +224 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/detect.d.ts +45 -0
  8. package/dist/detect.d.ts.map +1 -0
  9. package/dist/detect.js +238 -0
  10. package/dist/detect.js.map +1 -0
  11. package/dist/errors.d.ts +75 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +120 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/index.d.ts +13 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +13 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mutation.d.ts +46 -0
  20. package/dist/mutation.d.ts.map +1 -0
  21. package/dist/mutation.js +67 -0
  22. package/dist/mutation.js.map +1 -0
  23. package/dist/provider.d.ts +159 -0
  24. package/dist/provider.d.ts.map +1 -0
  25. package/dist/provider.js +142 -0
  26. package/dist/provider.js.map +1 -0
  27. package/dist/react.d.ts +76 -0
  28. package/dist/react.d.ts.map +1 -0
  29. package/dist/react.js +225 -0
  30. package/dist/react.js.map +1 -0
  31. package/dist/types.d.ts +391 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +38 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/vanilla.d.ts +58 -0
  36. package/dist/vanilla.d.ts.map +1 -0
  37. package/dist/vanilla.js +161 -0
  38. package/dist/vanilla.js.map +1 -0
  39. package/package.json +70 -0
  40. package/src/client.ts +342 -0
  41. package/src/detect.ts +278 -0
  42. package/src/errors.ts +133 -0
  43. package/src/index.ts +97 -0
  44. package/src/mutation.ts +109 -0
  45. package/src/provider.ts +229 -0
  46. package/src/react.ts +375 -0
  47. package/src/types.ts +440 -0
  48. package/src/vanilla.ts +239 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vanilla.js","sourceRoot":"","sources":["../src/vanilla.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@joeywallet/wallet-sdk",
3
+ "version": "0.2.0",
4
+ "description": "Dependency-free TypeScript SDK for the Joey Wallet browser extension (XLS-72d / Wallet Standard).",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/Joey-Wallet/joey-wallet-sdk#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Joey-Wallet/joey-wallet-sdk.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Joey-Wallet/joey-wallet-sdk/issues"
14
+ },
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./react": {
25
+ "types": "./dist/react.d.ts",
26
+ "import": "./dist/react.js"
27
+ },
28
+ "./vanilla": {
29
+ "types": "./dist/vanilla.d.ts",
30
+ "import": "./dist/vanilla.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src",
36
+ "LICENSE",
37
+ "README.md"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "peerDependencies": {
46
+ "react": ">=18"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "react": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "devDependencies": {
54
+ "@types/react": "19.0.2",
55
+ "react": "19.0.0",
56
+ "typescript": "5.7.2",
57
+ "xrpl": "4.2.5"
58
+ },
59
+ "keywords": [
60
+ "xrpl",
61
+ "wallet",
62
+ "joey",
63
+ "xls-72d",
64
+ "wallet-standard"
65
+ ],
66
+ "scripts": {
67
+ "build": "tsc -p tsconfig.json",
68
+ "ts:check": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json"
69
+ }
70
+ }
package/src/client.ts ADDED
@@ -0,0 +1,342 @@
1
+ /**
2
+ * The typed API a dapp actually calls.
3
+ *
4
+ * Every method resolves a value or rejects with a {@link JoeyRpcError}. There is
5
+ * deliberately no `{ type, result }` envelope: an envelope forces a branch at
6
+ * every call site, and a promise rejection is what a JS developer already writes
7
+ * `try`/`catch` around.
8
+ *
9
+ * Event payloads are normalised here rather than passed through raw. The
10
+ * provider forwards whatever the background sent, which is the right choice for
11
+ * a protocol layer and the wrong one for a dapp — a listener should not have to
12
+ * guess whether `accountsChanged` carried an array, an object with an
13
+ * `accounts` key, or a list of bare address strings.
14
+ */
15
+ import { JOEY_ERROR_CODES, JoeyRpcError } from './errors.js'
16
+ import {
17
+ JOEY_RPC_METHODS,
18
+ invoke,
19
+ subscribe,
20
+ type JoeyInjectedProvider,
21
+ type JoeyProviderEventName,
22
+ type JoeyRequestArguments,
23
+ type JoeyRpcMethod,
24
+ } from './provider.js'
25
+ import {
26
+ isJoeyChain,
27
+ networkIdForChain,
28
+ type AnyTransaction,
29
+ type ConnectParams,
30
+ type ConnectResult,
31
+ type JoeyAccount,
32
+ type JoeyChain,
33
+ type JoeyEventListener,
34
+ type JoeyEventName,
35
+ type JoeyNetwork,
36
+ type SignAndSubmitTransactionResult,
37
+ type SignInParams,
38
+ type SignInResult,
39
+ type SignTransactionBulkParams,
40
+ type SignTransactionForParams,
41
+ type SignTransactionParams,
42
+ type SignTransactionResult,
43
+ type TransactionLike,
44
+ } from './types.js'
45
+
46
+ export interface Joey {
47
+ /** The raw injected object, for anything this SDK version does not model. */
48
+ readonly provider: JoeyInjectedProvider
49
+ /** Version of the injected surface, when the provider reports one. */
50
+ readonly version: string | undefined
51
+ /** Reverse-DNS identity, `xyz.joeywallet`. */
52
+ readonly rdns: string | undefined
53
+ /** Granted addresses, read synchronously. `[]` until the user connects. */
54
+ readonly accounts: readonly string[]
55
+ /** The chain the wallet is on, or `null` before the first connect. */
56
+ readonly chain: JoeyChain | null
57
+
58
+ /** Synchronous. True once the user has granted this origin an account. */
59
+ isConnected(): boolean
60
+
61
+ connect(params?: ConnectParams): Promise<ConnectResult>
62
+ disconnect(): Promise<void>
63
+ /** Granted addresses. `[]` for an origin with no grant — never an error. */
64
+ getAccounts(): Promise<string[]>
65
+ getNetwork(): Promise<JoeyNetwork>
66
+
67
+ signTransaction<TTx extends TransactionLike = AnyTransaction>(
68
+ params: SignTransactionParams<TTx>,
69
+ ): Promise<SignTransactionResult>
70
+
71
+ signAndSubmitTransaction<TTx extends TransactionLike = AnyTransaction>(
72
+ params: SignTransactionParams<TTx>,
73
+ ): Promise<SignAndSubmitTransactionResult>
74
+
75
+ /** Add one signature to a multisigned transaction. */
76
+ signTransactionFor<TTx extends TransactionLike = AnyTransaction>(
77
+ params: SignTransactionForParams<TTx>,
78
+ ): Promise<SignTransactionResult>
79
+
80
+ /**
81
+ * One approval, up to `MAX_BULK_TRANSACTIONS` transactions, signed in order.
82
+ *
83
+ * Resolves with one entry per transaction, in the order you sent them.
84
+ * `engine_result` is present only when you asked for `submit: true` — with
85
+ * `submit: false` nothing is broadcast, so there is no engine result to give.
86
+ *
87
+ * A batch that fails part way *rejects*, and the error's `data` is a
88
+ * {@link SignTransactionBulkFailure} carrying every signed blob and which
89
+ * index stopped it. Read that rather than re-prompting the user.
90
+ */
91
+ signTransactionBulk<TTx extends TransactionLike = AnyTransaction>(
92
+ params: SignTransactionBulkParams<TTx>,
93
+ ): Promise<SignAndSubmitTransactionResult[]>
94
+
95
+ signIn(params?: SignInParams): Promise<SignInResult>
96
+
97
+ /** Escape hatch for a method the wallet added after this SDK was published. */
98
+ request<TResult = unknown>(args: JoeyRequestArguments): Promise<TResult>
99
+
100
+ on<K extends JoeyEventName>(event: K, listener: JoeyEventListener<K>): () => void
101
+ off<K extends JoeyEventName>(event: K, listener: JoeyEventListener<K>): void
102
+ }
103
+
104
+ /* --------------------------------------------------------------- normalising */
105
+
106
+ function isRecord(value: unknown): value is Record<string, unknown> {
107
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
108
+ }
109
+
110
+ /**
111
+ * Read an account list out of whatever the wallet sent.
112
+ *
113
+ * Accepts a bare array, `{ accounts: [...] }`, and entries that are either
114
+ * address strings or objects — the shapes the provider and the background
115
+ * between them can produce.
116
+ */
117
+ export function readAccounts(value: unknown): JoeyAccount[] {
118
+ const source = Array.isArray(value) ? value : isRecord(value) ? value.accounts : undefined
119
+ if (!Array.isArray(source)) return []
120
+
121
+ const out: JoeyAccount[] = []
122
+ for (const entry of source) {
123
+ if (typeof entry === 'string') {
124
+ out.push({ address: entry })
125
+ continue
126
+ }
127
+ if (!isRecord(entry) || typeof entry.address !== 'string') continue
128
+ const account: JoeyAccount = { address: entry.address }
129
+ if (typeof entry.publicKey === 'string') account.publicKey = entry.publicKey
130
+ if (typeof entry.label === 'string') account.label = entry.label
131
+ out.push(account)
132
+ }
133
+ return out
134
+ }
135
+
136
+ export function readChain(value: unknown): JoeyChain | null {
137
+ if (isJoeyChain(value)) return value
138
+ if (!isRecord(value)) return null
139
+ return isJoeyChain(value.chain) ? value.chain : null
140
+ }
141
+
142
+ export function readNetwork(value: unknown): JoeyNetwork | null {
143
+ const chain = readChain(value)
144
+ if (chain === null) return null
145
+ const record = isRecord(value) ? value : {}
146
+ return {
147
+ chain,
148
+ networkId:
149
+ typeof record.networkId === 'number' ? record.networkId : (networkIdForChain(chain) ?? 0),
150
+ name: typeof record.name === 'string' ? record.name : chain,
151
+ }
152
+ }
153
+
154
+ /* -------------------------------------------------------------------- client */
155
+
156
+ /** Wrap a raw injected provider in the typed API. */
157
+ export function createJoeyClient(provider: JoeyInjectedProvider): Joey {
158
+ /**
159
+ * Unsubscribe functions, keyed by the exact listener the caller handed us.
160
+ *
161
+ * Needed because the SDK subscribes a *wrapper* that normalises the payload,
162
+ * so the provider never sees the caller's own function.
163
+ *
164
+ * The value is a stack, not a single function, because a listener may be
165
+ * registered more than once and each registration is a separate subscription
166
+ * on the provider. Storing one function per listener silently dropped the
167
+ * earlier registration's unsubscriber: `off()` then removed the newest, the
168
+ * oldest kept firing, and nothing the dapp could call would ever remove it.
169
+ * A React effect that re-subscribes on a dependency change and unsubscribes
170
+ * on cleanup does exactly this, in that order, on every render — so the leak
171
+ * compounds. Each `off()` removes one registration, newest first, which is
172
+ * what `addEventListener`-shaped APIs do.
173
+ */
174
+ const unsubscribers = new Map<JoeyEventName, Map<unknown, Array<() => void>>>()
175
+
176
+ const call = async <TResult>(method: JoeyRpcMethod, params?: unknown): Promise<TResult> => {
177
+ try {
178
+ return await invoke<TResult>(provider, method, params)
179
+ } catch (cause) {
180
+ throw JoeyRpcError.from(cause)
181
+ }
182
+ }
183
+
184
+ /** Drop one specific registration, wherever it sits in the stack. */
185
+ const release = (
186
+ event: JoeyEventName,
187
+ listener: unknown,
188
+ unsubscribe: () => void,
189
+ ): void => {
190
+ const forEvent = unsubscribers.get(event)
191
+ const stack = forEvent?.get(listener)
192
+ if (stack === undefined) return
193
+ const at = stack.lastIndexOf(unsubscribe)
194
+ if (at === -1) return
195
+ stack.splice(at, 1)
196
+ if (stack.length === 0) forEvent?.delete(listener)
197
+ unsubscribe()
198
+ }
199
+
200
+ const normalise: {
201
+ [K in JoeyEventName]: (payload: unknown) => Parameters<JoeyEventListener<K>>[0]
202
+ } = {
203
+ connect: (payload) => ({ accounts: readAccounts(payload), chain: readChain(payload) }),
204
+ disconnect: (payload) =>
205
+ isRecord(payload) && typeof payload.reason === 'string' ? { reason: payload.reason } : {},
206
+ accountsChanged: (payload) => readAccounts(payload),
207
+ networkChanged: (payload) => readNetwork(payload),
208
+ }
209
+
210
+ const client: Joey = {
211
+ provider,
212
+ version: typeof provider.version === 'string' ? provider.version : undefined,
213
+ rdns: typeof provider.rdns === 'string' ? provider.rdns : undefined,
214
+
215
+ get accounts() {
216
+ return provider.accounts ?? []
217
+ },
218
+
219
+ get chain() {
220
+ return provider.chain ?? null
221
+ },
222
+
223
+ isConnected() {
224
+ if (typeof provider.isConnected === 'function') return provider.isConnected()
225
+ return (provider.accounts ?? []).length > 0
226
+ },
227
+
228
+ async connect(params) {
229
+ const result = await call<unknown>(JOEY_RPC_METHODS.connect, params ?? {})
230
+ const record = isRecord(result) ? result : {}
231
+ const chain = readChain(record)
232
+ return {
233
+ accounts: readAccounts(record),
234
+ chain,
235
+ networkId:
236
+ typeof record.networkId === 'number'
237
+ ? record.networkId
238
+ : chain === null
239
+ ? null
240
+ : networkIdForChain(chain),
241
+ }
242
+ },
243
+
244
+ async disconnect() {
245
+ await call<unknown>(JOEY_RPC_METHODS.disconnect, {})
246
+ },
247
+
248
+ async getAccounts() {
249
+ const result = await call<unknown>(JOEY_RPC_METHODS.getAccounts, {})
250
+ // The provider answers with bare addresses; the background may answer with
251
+ // account objects. Both collapse to addresses here.
252
+ return readAccounts(result).map((account) => account.address)
253
+ },
254
+
255
+ async getNetwork() {
256
+ const result = await call<unknown>(JOEY_RPC_METHODS.getNetwork, {})
257
+ const network = readNetwork(result)
258
+ if (network === null) {
259
+ throw new JoeyRpcError(
260
+ JOEY_ERROR_CODES.UNRECOGNIZED_CHAIN,
261
+ 'The wallet reported a chain this SDK does not recognise.',
262
+ )
263
+ }
264
+ return network
265
+ },
266
+
267
+ signTransaction(params) {
268
+ return call<SignTransactionResult>(JOEY_RPC_METHODS.signTransaction, params)
269
+ },
270
+
271
+ signAndSubmitTransaction(params) {
272
+ return call<SignAndSubmitTransactionResult>(
273
+ JOEY_RPC_METHODS.signAndSubmitTransaction,
274
+ params,
275
+ )
276
+ },
277
+
278
+ signTransactionFor(params) {
279
+ return call<SignTransactionResult>(JOEY_RPC_METHODS.signTransactionFor, params)
280
+ },
281
+
282
+ signTransactionBulk(params) {
283
+ return call<SignAndSubmitTransactionResult[]>(
284
+ JOEY_RPC_METHODS.signTransactionBulk,
285
+ params,
286
+ )
287
+ },
288
+
289
+ signIn(params) {
290
+ return call<SignInResult>(JOEY_RPC_METHODS.signIn, params ?? {})
291
+ },
292
+
293
+ async request(args) {
294
+ try {
295
+ return await provider.request(args)
296
+ } catch (cause) {
297
+ throw JoeyRpcError.from(cause)
298
+ }
299
+ },
300
+
301
+ on(event, listener) {
302
+ const wrapped = (payload: unknown): void => {
303
+ ;(listener as (value: unknown) => void)(normalise[event](payload))
304
+ }
305
+ const unsubscribe = subscribe(
306
+ provider,
307
+ event as JoeyProviderEventName,
308
+ wrapped as (payload: never) => void,
309
+ )
310
+
311
+ let forEvent = unsubscribers.get(event)
312
+ if (forEvent === undefined) {
313
+ forEvent = new Map()
314
+ unsubscribers.set(event, forEvent)
315
+ }
316
+ const stack = forEvent.get(listener)
317
+ if (stack === undefined) forEvent.set(listener, [unsubscribe])
318
+ else stack.push(unsubscribe)
319
+
320
+ // The returned function removes *this* registration, once. Calling it
321
+ // twice must not take a sibling registration down with it.
322
+ let released = false
323
+ return () => {
324
+ if (released) return
325
+ released = true
326
+ release(event, listener, unsubscribe)
327
+ }
328
+ },
329
+
330
+ off(event, listener) {
331
+ const stack = unsubscribers.get(event)?.get(listener)
332
+ const unsubscribe = stack?.pop()
333
+ if (unsubscribe === undefined) return
334
+ unsubscribe()
335
+ if (stack !== undefined && stack.length === 0) {
336
+ unsubscribers.get(event)?.delete(listener)
337
+ }
338
+ },
339
+ }
340
+
341
+ return Object.freeze(client)
342
+ }