@joeywallet/gemwallet-compat 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.
package/src/convert.ts ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * GemWallet request shapes to XRPL transaction JSON.
3
+ *
4
+ * GemWallet's payloads are camelCase and its memo/signer wrappers are lowercase
5
+ * (`{ memo: { memoData } }`), while the ledger — and therefore everything Joey
6
+ * signs — uses XRPL's PascalCase field names. Every mapping here is mechanical;
7
+ * it is separated out because a mistake in it would show up as a transaction
8
+ * that silently loses a destination tag or a memo.
9
+ */
10
+ import type {
11
+ AnyTransaction,
12
+ JoeyChain,
13
+ JoeyNetwork,
14
+ Memo as XrplMemo,
15
+ Signer as XrplSigner,
16
+ } from '@joeywallet/wallet-sdk'
17
+
18
+ import type {
19
+ BaseTransactionRequest,
20
+ Memo as GemMemo,
21
+ Network,
22
+ SendPaymentRequest,
23
+ SetTrustlineRequest,
24
+ Signer as GemSigner,
25
+ } from './types.js'
26
+
27
+ /** Drops keys whose value is `undefined` so the wire message stays minimal. */
28
+ function defined<T extends Record<string, unknown>>(source: T): Record<string, unknown> {
29
+ const out: Record<string, unknown> = {}
30
+ for (const [key, value] of Object.entries(source)) {
31
+ if (value !== undefined) out[key] = value
32
+ }
33
+ return out
34
+ }
35
+
36
+ export function toXrplMemos(memos: GemMemo[] | undefined): XrplMemo[] | undefined {
37
+ if (memos === undefined) return undefined
38
+ return memos.map((entry) => ({
39
+ Memo: defined({
40
+ MemoData: entry.memo.memoData,
41
+ MemoType: entry.memo.memoType,
42
+ MemoFormat: entry.memo.memoFormat,
43
+ }) as XrplMemo['Memo'],
44
+ }))
45
+ }
46
+
47
+ export function toXrplSigners(signers: GemSigner[] | undefined): XrplSigner[] | undefined {
48
+ if (signers === undefined) return undefined
49
+ return signers.map((entry) => ({
50
+ Signer: {
51
+ Account: entry.signer.account,
52
+ TxnSignature: entry.signer.txnSignature,
53
+ SigningPubKey: entry.signer.signingPubKey,
54
+ },
55
+ }))
56
+ }
57
+
58
+ /** The fields every GemWallet transaction request shares. */
59
+ export function toXrplCommonFields(request: BaseTransactionRequest): Record<string, unknown> {
60
+ return defined({
61
+ Fee: request.fee,
62
+ Sequence: request.sequence,
63
+ AccountTxnID: request.accountTxnID,
64
+ LastLedgerSequence: request.lastLedgerSequence,
65
+ Memos: toXrplMemos(request.memos),
66
+ NetworkID: request.networkID,
67
+ Signers: toXrplSigners(request.signers),
68
+ SourceTag: request.sourceTag,
69
+ SigningPubKey: request.signingPubKey,
70
+ TicketSequence: request.ticketSequence,
71
+ TxnSignature: request.txnSignature,
72
+ })
73
+ }
74
+
75
+ /**
76
+ * GemWallet's `sendPayment` payload as an XRPL `Payment`.
77
+ *
78
+ * **`amount` crosses untouched, and that is correct — checked, not assumed.**
79
+ * It is the one line in this package where being wrong costs a factor of a
80
+ * million, so the source is named rather than trusted to memory:
81
+ * `@gemwallet/api@3.8.0` types `SendPaymentRequest.amount` as xrpl.js's
82
+ * `Amount`, and `packages/constants/src/payload/payload.types.ts` documents it
83
+ * verbatim as *"A string representing the number of XRP to deliver, in drops."*
84
+ * Joey's `Payment.Amount` is drops. Same unit, no conversion, and a converting
85
+ * shim here would be the bug rather than the fix.
86
+ *
87
+ * The one place GemWallet does convert is its extension's `parseAmount`, and
88
+ * only for its deprecated v1 URL-parameter path, where `amount` arrives as a
89
+ * *number* of XRP. That path is not this API and never reaches this function:
90
+ * `amount` here is a `string` or an issued-currency object, both of which
91
+ * GemWallet forwards to the ledger exactly as this does.
92
+ *
93
+ * `convert.test.ts` pins both halves — drops for XRP, `value` untouched for an
94
+ * issued currency — so a future "helpful" `xrpToDrops` fails a test instead of
95
+ * a user's payment.
96
+ */
97
+ export function toPaymentTransaction(
98
+ request: SendPaymentRequest,
99
+ account?: string,
100
+ ): AnyTransaction {
101
+ return {
102
+ TransactionType: 'Payment',
103
+ ...(account === undefined ? {} : { Account: account }),
104
+ ...toXrplCommonFields(request),
105
+ ...defined({
106
+ // Drops. See the note above before changing this.
107
+ Amount: request.amount,
108
+ Destination: request.destination,
109
+ DestinationTag: request.destinationTag,
110
+ InvoiceID: request.invoiceID,
111
+ Paths: request.paths,
112
+ SendMax: request.sendMax,
113
+ DeliverMin: request.deliverMin,
114
+ Flags: request.flags,
115
+ }),
116
+ } as AnyTransaction
117
+ }
118
+
119
+ export function toTrustSetTransaction(
120
+ request: SetTrustlineRequest,
121
+ account?: string,
122
+ ): AnyTransaction {
123
+ return {
124
+ TransactionType: 'TrustSet',
125
+ ...(account === undefined ? {} : { Account: account }),
126
+ ...toXrplCommonFields(request),
127
+ ...defined({
128
+ LimitAmount: request.limitAmount,
129
+ QualityIn: request.qualityIn,
130
+ QualityOut: request.qualityOut,
131
+ Flags: request.flags,
132
+ }),
133
+ } as AnyTransaction
134
+ }
135
+
136
+ /* ------------------------------------------------------------------ networks */
137
+
138
+ /**
139
+ * The public endpoints for each chain.
140
+ *
141
+ * Duplicated from the extension's `shared/types.ts` `NETWORKS` rather than
142
+ * imported: this package is published to npm and cannot depend on the
143
+ * extension's source. GemWallet's `getNetwork()` contract promises a
144
+ * `websocket`, and dapps feed it straight into an xrpl.js `Client`, so
145
+ * answering with an empty string would break every migrating caller. They must
146
+ * be kept in step with the extension if an endpoint ever moves.
147
+ */
148
+ const WEBSOCKET_BY_CHAIN: Record<JoeyChain, string> = {
149
+ 'xrpl:0': 'wss://s1.ripple.com/',
150
+ 'xrpl:1': 'wss://testnet.xrpl-labs.com/',
151
+ 'xrpl:2': 'wss://s.devnet.rippletest.net:51233/',
152
+ }
153
+
154
+ const GEM_NAME_BY_CHAIN: Record<JoeyChain, Network> = {
155
+ 'xrpl:0': 'Mainnet',
156
+ 'xrpl:1': 'Testnet',
157
+ 'xrpl:2': 'Devnet',
158
+ }
159
+
160
+ const DESCRIPTION_BY_CHAIN: Record<JoeyChain, string> = {
161
+ 'xrpl:0': 'Main XRPL network',
162
+ 'xrpl:1': 'XRPL Testnet',
163
+ 'xrpl:2': 'XRPL Devnet',
164
+ }
165
+
166
+ /**
167
+ * Joey's network to GemWallet's `Network` string.
168
+ *
169
+ * GemWallet also has `Custom`, which Joey never returns: the extension ships a
170
+ * fixed set of three endpoints.
171
+ */
172
+ export function toGemNetwork(network: JoeyNetwork): Network {
173
+ return GEM_NAME_BY_CHAIN[network.chain]
174
+ }
175
+
176
+ export function toGemWebsocket(network: JoeyNetwork): string {
177
+ return WEBSOCKET_BY_CHAIN[network.chain]
178
+ }
179
+
180
+ /** GemWallet's human-readable network description, for the event payload. */
181
+ export function toGemNetworkDescription(network: JoeyNetwork): string {
182
+ return DESCRIPTION_BY_CHAIN[network.chain]
183
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * GemWallet's `{ type, result }` envelope, reconstructed on top of Joey's
3
+ * promise-rejection API.
4
+ *
5
+ * The two models disagree about exactly one thing, and this file is where that
6
+ * disagreement is resolved: GemWallet reports a user declining as a *resolved*
7
+ * `{ type: 'reject' }`, and reports everything else by throwing. Joey rejects
8
+ * with a `JoeyRpcError` in both cases. So a 4001 becomes `{ type: 'reject' }`
9
+ * and every other code is rethrown, which is what a GemWallet dapp's existing
10
+ * `if (result.type === 'reject')` branch and its `try`/`catch` already expect.
11
+ */
12
+ import { JoeyRpcError, isUserRejection } from '@joeywallet/wallet-sdk'
13
+
14
+ import type { BaseResponse } from './types.js'
15
+
16
+ export function response<T>(result: T): BaseResponse<T> & { type: 'response'; result: T } {
17
+ return { type: 'response', result }
18
+ }
19
+
20
+ export function rejected<T>(): BaseResponse<T> & { type: 'reject' } {
21
+ return { type: 'reject', result: undefined }
22
+ }
23
+
24
+ export async function envelope<T>(run: () => Promise<T>): Promise<BaseResponse<T>> {
25
+ try {
26
+ return response(await run())
27
+ } catch (cause) {
28
+ if (isUserRejection(cause)) return rejected<T>()
29
+ throw JoeyRpcError.from(cause)
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,394 @@
1
+ /**
2
+ * `@joeywallet/gemwallet-compat` — GemWallet's `@gemwallet/api` surface over Joey.
3
+ *
4
+ * - import { isInstalled, getAddress, sendPayment } from '@gemwallet/api'
5
+ * + import { isInstalled, getAddress, sendPayment } from '@joeywallet/gemwallet-compat'
6
+ *
7
+ * Function names, argument shapes and the `{ type, result }` envelope match
8
+ * `@gemwallet/api@3.8.0`. Four functions deliberately do not exist as working
9
+ * calls — see `./unsupported.ts`.
10
+ */
11
+ import {
12
+ JOEY_ERROR_CODES,
13
+ JoeyRpcError,
14
+ getJoey,
15
+ waitForJoey,
16
+ type Joey,
17
+ type JoeyAccount,
18
+ type JoeyNetwork,
19
+ } from '@joeywallet/wallet-sdk'
20
+
21
+ import {
22
+ toGemNetwork,
23
+ toGemNetworkDescription,
24
+ toGemWebsocket,
25
+ toPaymentTransaction,
26
+ toTrustSetTransaction,
27
+ } from './convert.js'
28
+ import { envelope } from './envelope.js'
29
+ import { refuse } from './unsupported.js'
30
+ import type {
31
+ EventLoginResponse,
32
+ EventLogoutResponse,
33
+ EventNetworkChangedResponse,
34
+ EventWalletChangedResponse,
35
+ GemEventPayloadMap,
36
+ GemEventType,
37
+ GetAddressResponse,
38
+ GetNetworkResponse,
39
+ GetPublicKeyResponse,
40
+ IsInstalledResponse,
41
+ SendPaymentRequest,
42
+ SendPaymentResponse,
43
+ SetTrustlineRequest,
44
+ SetTrustlineResponse,
45
+ SignMessageResponse,
46
+ SignTransactionRequest,
47
+ SignTransactionResponse,
48
+ SubmitBulkTransactionsRequest,
49
+ SubmitBulkTransactionsResponse,
50
+ SubmitTransactionRequest,
51
+ SubmitTransactionResponse,
52
+ TransactionBulkResponse,
53
+ } from './types.js'
54
+
55
+ /**
56
+ * How long to wait for a provider that has not been injected yet.
57
+ *
58
+ * 1000ms is GemWallet's own number, and dapps written against it already treat
59
+ * a slower answer as "not installed".
60
+ */
61
+ const DETECT_TIMEOUT_MS = 1000
62
+
63
+ async function requireProvider(): Promise<Joey> {
64
+ const immediate = getJoey()
65
+ if (immediate !== null) return immediate
66
+ return await waitForJoey({ timeoutMs: DETECT_TIMEOUT_MS })
67
+ }
68
+
69
+ /**
70
+ * The account this origin may use, connecting first if it has to.
71
+ *
72
+ * `connect({ silent: true })` rather than `getAccounts()` because GemWallet's
73
+ * `getPublicKey()` needs the public key, and only the connect result carries
74
+ * it — `getAccounts()` answers with bare addresses. A silent connect resolves
75
+ * with an empty list rather than throwing when the origin has no grant, so the
76
+ * non-silent call below is what actually opens the approval window, matching
77
+ * GemWallet's behaviour of prompting from `getAddress()`.
78
+ */
79
+ async function requireAccount(): Promise<{ joey: Joey; account: JoeyAccount }> {
80
+ const joey = await requireProvider()
81
+
82
+ let result = await joey.connect({ silent: true })
83
+ if (result.accounts.length === 0) result = await joey.connect()
84
+
85
+ const account = result.accounts[0]
86
+ if (account === undefined) {
87
+ throw new JoeyRpcError(
88
+ JOEY_ERROR_CODES.UNAUTHORIZED,
89
+ 'Joey Wallet connected without sharing an account.',
90
+ )
91
+ }
92
+ return { joey, account }
93
+ }
94
+
95
+ /* ------------------------------------------------------------------ detection */
96
+
97
+ /**
98
+ * Never rejects, and answers immediately when the provider is already present.
99
+ *
100
+ * Matches GemWallet's contract, including its 1-second budget for a provider
101
+ * that has not been injected yet.
102
+ */
103
+ export async function isInstalled(): Promise<IsInstalledResponse> {
104
+ if (getJoey() !== null) return { result: { isInstalled: true } }
105
+ try {
106
+ await waitForJoey({ timeoutMs: DETECT_TIMEOUT_MS })
107
+ return { result: { isInstalled: true } }
108
+ } catch {
109
+ return { result: { isInstalled: false } }
110
+ }
111
+ }
112
+
113
+ /* -------------------------------------------------------------------- account */
114
+
115
+ export async function getAddress(): Promise<GetAddressResponse> {
116
+ return await envelope(async () => {
117
+ const { account } = await requireAccount()
118
+ return { address: account.address }
119
+ })
120
+ }
121
+
122
+ export async function getPublicKey(): Promise<GetPublicKeyResponse> {
123
+ return await envelope(async () => {
124
+ const { account } = await requireAccount()
125
+ if (account.publicKey === undefined) {
126
+ throw new JoeyRpcError(
127
+ JOEY_ERROR_CODES.UNAUTHORIZED,
128
+ 'The selected Joey account is watch-only and has no public key.',
129
+ )
130
+ }
131
+ return { address: account.address, publicKey: account.publicKey }
132
+ })
133
+ }
134
+
135
+ export async function getNetwork(): Promise<GetNetworkResponse> {
136
+ return await envelope(async () => {
137
+ const joey = await requireProvider()
138
+ const network = await joey.getNetwork()
139
+ return {
140
+ // Joey is XRPL-only. GemWallet's other value, XAHAU, is never returned.
141
+ chain: 'XRPL',
142
+ network: toGemNetwork(network),
143
+ websocket: toGemWebsocket(network),
144
+ }
145
+ })
146
+ }
147
+
148
+ /* -------------------------------------------------------------------- signing */
149
+
150
+ /**
151
+ * Not implemented. Throws {@link GemWalletUnsupportedError} synchronously.
152
+ *
153
+ * Joey has no raw message-signing method: a bare signature over a string
154
+ * carries no domain, nonce or timestamp and is replayable against another site.
155
+ * Use `signIn()` from `@joeywallet/wallet-sdk`, which signs a CAIP-122 message bound
156
+ * to this origin.
157
+ */
158
+ export function signMessage(_message: string, _isHex?: boolean): Promise<SignMessageResponse> {
159
+ return refuse('signMessage')
160
+ }
161
+
162
+ export async function sendPayment(
163
+ paymentPayload: SendPaymentRequest,
164
+ ): Promise<SendPaymentResponse> {
165
+ return await envelope(async () => {
166
+ const { joey, account } = await requireAccount()
167
+ const result = await joey.signAndSubmitTransaction({
168
+ tx_json: toPaymentTransaction(paymentPayload, account.address),
169
+ })
170
+ return { hash: result.hash }
171
+ })
172
+ }
173
+
174
+ export async function setTrustline(
175
+ payload: SetTrustlineRequest,
176
+ ): Promise<SetTrustlineResponse> {
177
+ return await envelope(async () => {
178
+ const { joey, account } = await requireAccount()
179
+ const result = await joey.signAndSubmitTransaction({
180
+ tx_json: toTrustSetTransaction(payload, account.address),
181
+ })
182
+ return { hash: result.hash }
183
+ })
184
+ }
185
+
186
+ export async function signTransaction(
187
+ payload: SignTransactionRequest,
188
+ ): Promise<SignTransactionResponse> {
189
+ return await envelope(async () => {
190
+ const { joey } = await requireAccount()
191
+ const result = await joey.signTransaction({ tx_json: payload.transaction })
192
+ // GemWallet calls the signed blob `signature`. It is the full signed
193
+ // transaction, not the `TxnSignature` field.
194
+ return { signature: result.tx_blob }
195
+ })
196
+ }
197
+
198
+ export async function submitTransaction(
199
+ payload: SubmitTransactionRequest,
200
+ ): Promise<SubmitTransactionResponse> {
201
+ return await envelope(async () => {
202
+ const { joey } = await requireAccount()
203
+ const result = await joey.signAndSubmitTransaction({ tx_json: payload.transaction })
204
+ return { hash: result.hash }
205
+ })
206
+ }
207
+
208
+ export async function submitBulkTransactions(
209
+ payload: SubmitBulkTransactionsRequest,
210
+ ): Promise<SubmitBulkTransactionsResponse> {
211
+ return await envelope(async () => {
212
+ const { joey } = await requireAccount()
213
+
214
+ // GemWallet correlates results by an `ID` field carried inside each
215
+ // transaction. `ID` is not an XRPL field and would break serialisation, so
216
+ // it is stripped here and re-attached by position — Joey signs the batch in
217
+ // the order it was given.
218
+ const ids: Array<string | undefined> = []
219
+ const tx_list = payload.transactions.map((entry) => {
220
+ const { ID, ...tx_json } = entry
221
+ ids.push(ID)
222
+ return { tx_json }
223
+ })
224
+
225
+ const results = await joey.signTransactionBulk({ tx_list, submit: true })
226
+
227
+ const transactions: TransactionBulkResponse[] = ids.map((id, index) => {
228
+ const result = results[index]
229
+ return {
230
+ ...(id === undefined ? {} : { id }),
231
+ // A resolved bulk request carries one entry per transaction, so every
232
+ // one of these is `true`. The guard stays because the alternative
233
+ // reading — an index with no entry silently becoming `accepted: true`
234
+ // with no hash — is the failure this shape exists to prevent.
235
+ //
236
+ // A batch that fails part way *rejects*, and `envelope` turns that into
237
+ // GemWallet's error response. The signed blobs and the failing index
238
+ // are on the error's `data` (`SignTransactionBulkFailure`); mapping
239
+ // them onto per-transaction `accepted` flags would be a better answer
240
+ // for a GemWallet dapp than an error, and is deliberately left as a
241
+ // change to this package's own contract rather than smuggled in with
242
+ // the wallet's.
243
+ accepted: result !== undefined,
244
+ ...(result === undefined ? {} : { hash: result.hash }),
245
+ }
246
+ })
247
+
248
+ return { transactions }
249
+ })
250
+ }
251
+
252
+ /* --------------------------------------------------------------- unsupported */
253
+
254
+ /**
255
+ * Not implemented. Throws {@link GemWalletUnsupportedError} synchronously.
256
+ *
257
+ * @see ./unsupported.ts for why.
258
+ */
259
+ export function setRegularKey(_payload?: unknown): Promise<never> {
260
+ return refuse('setRegularKey')
261
+ }
262
+
263
+ /** Not implemented. Throws {@link GemWalletUnsupportedError} synchronously. */
264
+ export function setHook(_payload?: unknown): Promise<never> {
265
+ return refuse('setHook')
266
+ }
267
+
268
+ /** Not implemented. Throws {@link GemWalletUnsupportedError} synchronously. */
269
+ export function setAccount(_payload?: unknown): Promise<never> {
270
+ return refuse('setAccount')
271
+ }
272
+
273
+ /* -------------------------------------------------------------------- events */
274
+
275
+ type NormalisedEvent<E extends GemEventType> = E extends 'login' | 'EVENT_LOGIN'
276
+ ? 'login'
277
+ : E extends 'logout' | 'EVENT_LOGOUT'
278
+ ? 'logout'
279
+ : E extends 'networkChanged' | 'EVENT_NETWORK_CHANGED'
280
+ ? 'networkChanged'
281
+ : E extends 'walletChanged' | 'EVENT_WALLET_CHANGED'
282
+ ? 'walletChanged'
283
+ : never
284
+
285
+ function normaliseEvent(eventType: GemEventType): keyof GemEventPayloadMap {
286
+ switch (eventType) {
287
+ case 'login':
288
+ case 'EVENT_LOGIN':
289
+ return 'login'
290
+ case 'logout':
291
+ case 'EVENT_LOGOUT':
292
+ return 'logout'
293
+ case 'networkChanged':
294
+ case 'EVENT_NETWORK_CHANGED':
295
+ return 'networkChanged'
296
+ case 'walletChanged':
297
+ case 'EVENT_WALLET_CHANGED':
298
+ return 'walletChanged'
299
+ }
300
+ }
301
+
302
+ function toGemNetworkEvent(network: JoeyNetwork): EventNetworkChangedResponse {
303
+ return {
304
+ network: {
305
+ name: toGemNetwork(network),
306
+ server: toGemWebsocket(network),
307
+ description: toGemNetworkDescription(network),
308
+ },
309
+ }
310
+ }
311
+
312
+ function attach(
313
+ joey: Joey,
314
+ event: keyof GemEventPayloadMap,
315
+ callback: (payload: never) => void,
316
+ ): () => void {
317
+ const emit = (payload: unknown): void => {
318
+ ;(callback as (value: unknown) => void)(payload)
319
+ }
320
+
321
+ switch (event) {
322
+ case 'login':
323
+ return joey.on('connect', () => emit({ loggedIn: true } satisfies EventLoginResponse))
324
+ case 'logout':
325
+ return joey.on('disconnect', () => emit({ loggedIn: false } satisfies EventLogoutResponse))
326
+ case 'networkChanged':
327
+ return joey.on('networkChanged', (network) => {
328
+ if (network !== null) emit(toGemNetworkEvent(network))
329
+ })
330
+ case 'walletChanged':
331
+ return joey.on('accountsChanged', (accounts) =>
332
+ emit({
333
+ wallet: { publicAddress: accounts[0]?.address ?? '' },
334
+ } satisfies EventWalletChangedResponse),
335
+ )
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Subscribe to a wallet event.
341
+ *
342
+ * `@gemwallet/api`'s `on()` returns `void`; this returns an unsubscribe
343
+ * function. That is a superset — existing call sites that ignore the return
344
+ * value are unaffected — and it is what a single-page app needs to avoid
345
+ * leaking a listener on every route change.
346
+ */
347
+ export function on<E extends GemEventType>(
348
+ eventType: E,
349
+ callback: (payload: GemEventPayloadMap[NormalisedEvent<E>]) => void,
350
+ ): () => void {
351
+ const event = normaliseEvent(eventType)
352
+ let detach: (() => void) | null = null
353
+ let cancelled = false
354
+
355
+ const bind = (joey: Joey): void => {
356
+ if (cancelled) return
357
+ detach = attach(joey, event, callback as (payload: never) => void)
358
+ }
359
+
360
+ const immediate = getJoey()
361
+ if (immediate !== null) bind(immediate)
362
+ else {
363
+ void waitForJoey({ timeoutMs: DETECT_TIMEOUT_MS })
364
+ .then(bind)
365
+ .catch(() => {
366
+ /* no wallet, nothing to listen to */
367
+ })
368
+ }
369
+
370
+ return () => {
371
+ cancelled = true
372
+ detach?.()
373
+ detach = null
374
+ }
375
+ }
376
+
377
+ /* -------------------------------------------------------------------- exports */
378
+
379
+ export {
380
+ GemWalletUnsupportedError,
381
+ UNSUPPORTED_METHODS,
382
+ type UnsupportedMethod,
383
+ } from './unsupported.js'
384
+ export { envelope, rejected, response } from './envelope.js'
385
+ export {
386
+ toGemNetwork,
387
+ toGemWebsocket,
388
+ toPaymentTransaction,
389
+ toTrustSetTransaction,
390
+ toXrplMemos,
391
+ toXrplSigners,
392
+ } from './convert.js'
393
+ export { DEFAULT_SUBMIT_TX_BULK_ON_ERROR } from './types.js'
394
+ export type * from './types.js'