@caatinga/client 2.1.0 → 2.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/README.md CHANGED
@@ -49,6 +49,8 @@ Supported runtime root exports:
49
49
  - `createDefaultBindingAdapter`
50
50
  - `CaatingaContractClient`
51
51
  - `buildXdr`
52
+ - `createWalletSession`
53
+ - `WALLET_SESSION_STORAGE_KEY`
52
54
 
53
55
  Supported type-only root exports:
54
56
 
@@ -62,12 +64,15 @@ Supported type-only root exports:
62
64
  - `CaatingaReadOptions`
63
65
  - `CaatingaReadResult`
64
66
  - `CaatingaWalletAdapter`
67
+ - `CaatingaWalletCapabilities`
65
68
  - `CaatingaXdrBuildResult`
69
+ - `WalletSession`, `WalletSessionOptions`, `WalletSessionState`, `WalletSessionStatus`, `WalletSessionStorage`
66
70
 
67
- Supported subpath export:
71
+ Supported subpath exports:
68
72
 
69
73
  - `@caatinga/client/freighter` -> `freighterWalletAdapter` (optional)
70
74
  - `@caatinga/client/stellar-wallets-kit` -> `createStellarWalletsKitAdapter` (optional)
75
+ - `@caatinga/client/react` -> `WalletProvider`, `useWallet`, `useWalletSession` (optional, needs `react >= 18`)
71
76
 
72
77
  Primary flow:
73
78
 
@@ -122,6 +127,33 @@ export interface CaatingaWalletAdapter {
122
127
 
123
128
  The default Freighter adapter is exported from `@caatinga/client/freighter`.
124
129
 
130
+ ## Wallet Session and React Hooks
131
+
132
+ Wrap any adapter with connection state, persistence, and silent restore:
133
+
134
+ ```ts
135
+ import { createWalletSession } from "@caatinga/client";
136
+
137
+ const session = createWalletSession(wallet, { persist: true });
138
+ await session.connect(); // modal when available, else getPublicKey()
139
+ await session.restore(); // silent reconnect on page load — never throws
140
+ session.subscribe(() => console.log(session.getState()));
141
+ ```
142
+
143
+ React apps (optional `react >= 18` peer) use the `react` subpath instead of a hand-rolled context:
144
+
145
+ ```tsx
146
+ import { WalletProvider, useWallet } from "@caatinga/client/react";
147
+
148
+ <WalletProvider adapter={wallet} options={{ persist: true }}>
149
+ <App />
150
+ </WalletProvider>;
151
+
152
+ const { publicKey, connected, connecting, error, connect, disconnect } = useWallet();
153
+ ```
154
+
155
+ Full guide: [docs/wallets.md](https://github.com/Dione-b/caatinga/blob/main/docs/wallets.md).
156
+
125
157
  ## Debug Output Rules
126
158
 
127
159
  - XDR data is omitted by default
@@ -151,5 +183,5 @@ Common codes include:
151
183
 
152
184
  - this package does not replace Stellar CLI, Stellar SDK, Soroban SDK, or generated bindings
153
185
  - manual SCVal serialization and manual XDR parsing are out of scope
154
- - React hooks, multisig orchestration, backend signing, and non-documented wallet integrations are not part of the supported contract
186
+ - multisig orchestration, backend signing, and non-documented wallet integrations are not part of the supported contract
155
187
  - private module paths and undocumented helpers are less stable than the exports listed above
@@ -0,0 +1,169 @@
1
+ // src/wallet/with-wallet-timeout.ts
2
+ import { CaatingaError, CaatingaErrorCode } from "@caatinga/core/browser";
3
+ function withWalletTimeout(label, timeoutMs, fn) {
4
+ if (timeoutMs === void 0 || timeoutMs <= 0) {
5
+ return fn();
6
+ }
7
+ let timedOut = false;
8
+ return new Promise((resolve, reject) => {
9
+ const timer = setTimeout(() => {
10
+ timedOut = true;
11
+ reject(
12
+ new CaatingaError(
13
+ `Wallet "${label}" timed out after ${timeoutMs}ms.`,
14
+ CaatingaErrorCode.WALLET_TIMEOUT,
15
+ "Ensure the wallet adapter rejects on user dismissal, or increase walletTimeout."
16
+ )
17
+ );
18
+ }, timeoutMs);
19
+ fn().then(
20
+ (value) => {
21
+ if (timedOut) {
22
+ return;
23
+ }
24
+ clearTimeout(timer);
25
+ resolve(value);
26
+ },
27
+ (error) => {
28
+ if (timedOut) {
29
+ return;
30
+ }
31
+ clearTimeout(timer);
32
+ reject(error);
33
+ }
34
+ );
35
+ });
36
+ }
37
+
38
+ // src/wallet/wallet-session.ts
39
+ var WALLET_SESSION_STORAGE_KEY = "caatinga:wallet-session";
40
+ function defaultStorage() {
41
+ if (typeof window === "undefined") {
42
+ return void 0;
43
+ }
44
+ try {
45
+ return window.localStorage;
46
+ } catch {
47
+ return void 0;
48
+ }
49
+ }
50
+ function toError(value) {
51
+ return value instanceof Error ? value : new Error(String(value));
52
+ }
53
+ function createWalletSession(adapter, options = {}) {
54
+ const capabilities = adapter;
55
+ const storage = options.persist ? options.storage ?? defaultStorage() : void 0;
56
+ const storageKey = options.storageKey ?? WALLET_SESSION_STORAGE_KEY;
57
+ const listeners = /* @__PURE__ */ new Set();
58
+ let state = {
59
+ status: "disconnected",
60
+ publicKey: null,
61
+ error: null
62
+ };
63
+ function setState(next) {
64
+ state = next;
65
+ for (const listener of listeners) {
66
+ listener();
67
+ }
68
+ }
69
+ function persistConnection() {
70
+ if (!storage) {
71
+ return;
72
+ }
73
+ const payload = { v: 1 };
74
+ const walletId = capabilities.getWalletId?.();
75
+ if (walletId) {
76
+ payload.walletId = walletId;
77
+ }
78
+ try {
79
+ storage.setItem(storageKey, JSON.stringify(payload));
80
+ } catch {
81
+ }
82
+ }
83
+ function clearPersisted() {
84
+ try {
85
+ storage?.removeItem(storageKey);
86
+ } catch {
87
+ }
88
+ }
89
+ function readPersisted() {
90
+ if (!storage) {
91
+ return null;
92
+ }
93
+ try {
94
+ const raw = storage.getItem(storageKey);
95
+ if (!raw) {
96
+ return null;
97
+ }
98
+ const parsed = JSON.parse(raw);
99
+ return parsed && parsed.v === 1 ? parsed : null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ async function requestPublicKey(label, viaModal) {
105
+ const request = viaModal && capabilities.openModal ? () => capabilities.openModal() : () => adapter.getPublicKey();
106
+ return withWalletTimeout(label, options.timeout, request);
107
+ }
108
+ async function connect() {
109
+ setState({ status: "connecting", publicKey: null, error: null });
110
+ try {
111
+ const publicKey = await requestPublicKey("connect", true);
112
+ setState({ status: "connected", publicKey, error: null });
113
+ persistConnection();
114
+ return publicKey;
115
+ } catch (error) {
116
+ setState({ status: "disconnected", publicKey: null, error: toError(error) });
117
+ throw error;
118
+ }
119
+ }
120
+ async function disconnect() {
121
+ try {
122
+ await capabilities.disconnect?.();
123
+ } finally {
124
+ clearPersisted();
125
+ setState({ status: "disconnected", publicKey: null, error: null });
126
+ }
127
+ }
128
+ async function restore() {
129
+ if (state.status === "connected") {
130
+ return state.publicKey;
131
+ }
132
+ const persisted = readPersisted();
133
+ if (!persisted) {
134
+ return null;
135
+ }
136
+ setState({ status: "connecting", publicKey: null, error: null });
137
+ try {
138
+ if (persisted.walletId) {
139
+ capabilities.setWallet?.(persisted.walletId);
140
+ }
141
+ const publicKey = await requestPublicKey("restore", false);
142
+ setState({ status: "connected", publicKey, error: null });
143
+ return publicKey;
144
+ } catch {
145
+ clearPersisted();
146
+ setState({ status: "disconnected", publicKey: null, error: null });
147
+ return null;
148
+ }
149
+ }
150
+ return {
151
+ adapter,
152
+ getState: () => state,
153
+ subscribe(listener) {
154
+ listeners.add(listener);
155
+ return () => {
156
+ listeners.delete(listener);
157
+ };
158
+ },
159
+ connect,
160
+ disconnect,
161
+ restore
162
+ };
163
+ }
164
+
165
+ export {
166
+ withWalletTimeout,
167
+ WALLET_SESSION_STORAGE_KEY,
168
+ createWalletSession
169
+ };
package/dist/index.cjs CHANGED
@@ -21,15 +21,181 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  CaatingaContractClient: () => CaatingaContractClient,
24
+ WALLET_SESSION_STORAGE_KEY: () => WALLET_SESSION_STORAGE_KEY,
24
25
  buildXdr: () => buildXdr,
25
26
  createCaatingaClient: () => createCaatingaClient,
26
27
  createDefaultBindingAdapter: () => createDefaultBindingAdapter,
28
+ createWalletSession: () => createWalletSession,
27
29
  resolveContractId: () => resolveContractId
28
30
  });
29
31
  module.exports = __toCommonJS(index_exports);
30
32
 
31
- // src/artifacts/resolve-contract-id.ts
33
+ // src/wallet/with-wallet-timeout.ts
32
34
  var import_browser = require("@caatinga/core/browser");
35
+ function withWalletTimeout(label, timeoutMs, fn) {
36
+ if (timeoutMs === void 0 || timeoutMs <= 0) {
37
+ return fn();
38
+ }
39
+ let timedOut = false;
40
+ return new Promise((resolve, reject) => {
41
+ const timer = setTimeout(() => {
42
+ timedOut = true;
43
+ reject(
44
+ new import_browser.CaatingaError(
45
+ `Wallet "${label}" timed out after ${timeoutMs}ms.`,
46
+ import_browser.CaatingaErrorCode.WALLET_TIMEOUT,
47
+ "Ensure the wallet adapter rejects on user dismissal, or increase walletTimeout."
48
+ )
49
+ );
50
+ }, timeoutMs);
51
+ fn().then(
52
+ (value) => {
53
+ if (timedOut) {
54
+ return;
55
+ }
56
+ clearTimeout(timer);
57
+ resolve(value);
58
+ },
59
+ (error) => {
60
+ if (timedOut) {
61
+ return;
62
+ }
63
+ clearTimeout(timer);
64
+ reject(error);
65
+ }
66
+ );
67
+ });
68
+ }
69
+
70
+ // src/wallet/wallet-session.ts
71
+ var WALLET_SESSION_STORAGE_KEY = "caatinga:wallet-session";
72
+ function defaultStorage() {
73
+ if (typeof window === "undefined") {
74
+ return void 0;
75
+ }
76
+ try {
77
+ return window.localStorage;
78
+ } catch {
79
+ return void 0;
80
+ }
81
+ }
82
+ function toError(value) {
83
+ return value instanceof Error ? value : new Error(String(value));
84
+ }
85
+ function createWalletSession(adapter, options = {}) {
86
+ const capabilities = adapter;
87
+ const storage = options.persist ? options.storage ?? defaultStorage() : void 0;
88
+ const storageKey = options.storageKey ?? WALLET_SESSION_STORAGE_KEY;
89
+ const listeners = /* @__PURE__ */ new Set();
90
+ let state = {
91
+ status: "disconnected",
92
+ publicKey: null,
93
+ error: null
94
+ };
95
+ function setState(next) {
96
+ state = next;
97
+ for (const listener of listeners) {
98
+ listener();
99
+ }
100
+ }
101
+ function persistConnection() {
102
+ if (!storage) {
103
+ return;
104
+ }
105
+ const payload = { v: 1 };
106
+ const walletId = capabilities.getWalletId?.();
107
+ if (walletId) {
108
+ payload.walletId = walletId;
109
+ }
110
+ try {
111
+ storage.setItem(storageKey, JSON.stringify(payload));
112
+ } catch {
113
+ }
114
+ }
115
+ function clearPersisted() {
116
+ try {
117
+ storage?.removeItem(storageKey);
118
+ } catch {
119
+ }
120
+ }
121
+ function readPersisted() {
122
+ if (!storage) {
123
+ return null;
124
+ }
125
+ try {
126
+ const raw = storage.getItem(storageKey);
127
+ if (!raw) {
128
+ return null;
129
+ }
130
+ const parsed = JSON.parse(raw);
131
+ return parsed && parsed.v === 1 ? parsed : null;
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+ async function requestPublicKey(label, viaModal) {
137
+ const request = viaModal && capabilities.openModal ? () => capabilities.openModal() : () => adapter.getPublicKey();
138
+ return withWalletTimeout(label, options.timeout, request);
139
+ }
140
+ async function connect() {
141
+ setState({ status: "connecting", publicKey: null, error: null });
142
+ try {
143
+ const publicKey = await requestPublicKey("connect", true);
144
+ setState({ status: "connected", publicKey, error: null });
145
+ persistConnection();
146
+ return publicKey;
147
+ } catch (error) {
148
+ setState({ status: "disconnected", publicKey: null, error: toError(error) });
149
+ throw error;
150
+ }
151
+ }
152
+ async function disconnect() {
153
+ try {
154
+ await capabilities.disconnect?.();
155
+ } finally {
156
+ clearPersisted();
157
+ setState({ status: "disconnected", publicKey: null, error: null });
158
+ }
159
+ }
160
+ async function restore() {
161
+ if (state.status === "connected") {
162
+ return state.publicKey;
163
+ }
164
+ const persisted = readPersisted();
165
+ if (!persisted) {
166
+ return null;
167
+ }
168
+ setState({ status: "connecting", publicKey: null, error: null });
169
+ try {
170
+ if (persisted.walletId) {
171
+ capabilities.setWallet?.(persisted.walletId);
172
+ }
173
+ const publicKey = await requestPublicKey("restore", false);
174
+ setState({ status: "connected", publicKey, error: null });
175
+ return publicKey;
176
+ } catch {
177
+ clearPersisted();
178
+ setState({ status: "disconnected", publicKey: null, error: null });
179
+ return null;
180
+ }
181
+ }
182
+ return {
183
+ adapter,
184
+ getState: () => state,
185
+ subscribe(listener) {
186
+ listeners.add(listener);
187
+ return () => {
188
+ listeners.delete(listener);
189
+ };
190
+ },
191
+ connect,
192
+ disconnect,
193
+ restore
194
+ };
195
+ }
196
+
197
+ // src/artifacts/resolve-contract-id.ts
198
+ var import_browser2 = require("@caatinga/core/browser");
33
199
  function resolveContractId(input) {
34
200
  if (input.explicitContractId) {
35
201
  return input.explicitContractId;
@@ -39,9 +205,9 @@ function resolveContractId(input) {
39
205
  return contractId;
40
206
  }
41
207
  const hint = formatMissingContractArtifactHint(input.artifacts, input.network, input.contract);
42
- throw new import_browser.CaatingaError(
208
+ throw new import_browser2.CaatingaError(
43
209
  `No contract artifact found for "${input.contract}" on "${input.network}".`,
44
- import_browser.CaatingaErrorCode.CONTRACT_ARTIFACT_NOT_FOUND,
210
+ import_browser2.CaatingaErrorCode.CONTRACT_ARTIFACT_NOT_FOUND,
45
211
  hint
46
212
  );
47
213
  }
@@ -72,21 +238,21 @@ function formatMissingContractArtifactHint(artifacts, network, contract) {
72
238
  }
73
239
 
74
240
  // src/bindings/default-binding-adapter.ts
75
- var import_browser2 = require("@caatinga/core/browser");
241
+ var import_browser3 = require("@caatinga/core/browser");
76
242
  function createDefaultBindingAdapter(binding) {
77
243
  return {
78
244
  createClient({ contractId, publicKey, rpcUrl, networkPassphrase }) {
79
245
  if (binding.__caatingaPlaceholder) {
80
- throw new import_browser2.CaatingaError(
246
+ throw new import_browser3.CaatingaError(
81
247
  "Placeholder bindings are still in use; the app cannot reach the contract.",
82
- import_browser2.CaatingaErrorCode.PLACEHOLDER_BINDING,
248
+ import_browser3.CaatingaErrorCode.PLACEHOLDER_BINDING,
83
249
  "Run `caatinga generate <contract> --network <network>`, then restart the dev server."
84
250
  );
85
251
  }
86
252
  if (!binding.Client) {
87
- throw new import_browser2.CaatingaError(
253
+ throw new import_browser3.CaatingaError(
88
254
  "Generated binding does not export Client.",
89
- import_browser2.CaatingaErrorCode.BINDING_CLIENT_NOT_FOUND,
255
+ import_browser3.CaatingaErrorCode.BINDING_CLIENT_NOT_FOUND,
90
256
  "Regenerate bindings with Stellar CLI."
91
257
  );
92
258
  }
@@ -101,9 +267,9 @@ function createDefaultBindingAdapter(binding) {
101
267
  const candidate = client;
102
268
  const fn = candidate[method];
103
269
  if (typeof fn !== "function") {
104
- throw new import_browser2.CaatingaError(
270
+ throw new import_browser3.CaatingaError(
105
271
  `Binding method "${method}" was not found.`,
106
- import_browser2.CaatingaErrorCode.BINDING_METHOD_NOT_FOUND,
272
+ import_browser3.CaatingaErrorCode.BINDING_METHOD_NOT_FOUND,
107
273
  "Check the contract method name or regenerate bindings."
108
274
  );
109
275
  }
@@ -119,7 +285,7 @@ var import_browser8 = require("@caatinga/core/browser");
119
285
  var import_browser7 = require("@caatinga/core/browser");
120
286
 
121
287
  // src/xdr/build-xdr.ts
122
- var import_browser3 = require("@caatinga/core/browser");
288
+ var import_browser4 = require("@caatinga/core/browser");
123
289
  async function buildXdr(input) {
124
290
  try {
125
291
  const transaction = input.transaction;
@@ -129,12 +295,12 @@ async function buildXdr(input) {
129
295
  try {
130
296
  preparedTransaction = await transaction.prepare();
131
297
  } catch (error) {
132
- if (error instanceof import_browser3.CaatingaError) {
298
+ if (error instanceof import_browser4.CaatingaError) {
133
299
  throw error;
134
300
  }
135
- throw new import_browser3.CaatingaError(
301
+ throw new import_browser4.CaatingaError(
136
302
  `Failed to prepare XDR for "${input.contractName}.${input.method}".`,
137
- import_browser3.CaatingaErrorCode.XDR_PREPARE_FAILED,
303
+ import_browser4.CaatingaErrorCode.XDR_PREPARE_FAILED,
138
304
  `RPC: ${input.rpcUrl}. Check connectivity, simulation errors, and binding compatibility.`,
139
305
  error
140
306
  );
@@ -152,12 +318,12 @@ async function buildXdr(input) {
152
318
  ...input.debug ? { raw: preparedTransaction } : {}
153
319
  };
154
320
  } catch (error) {
155
- if (error instanceof import_browser3.CaatingaError) {
321
+ if (error instanceof import_browser4.CaatingaError) {
156
322
  throw error;
157
323
  }
158
- throw new import_browser3.CaatingaError(
324
+ throw new import_browser4.CaatingaError(
159
325
  `Failed to build XDR for "${input.contractName}.${input.method}".`,
160
- import_browser3.CaatingaErrorCode.XDR_BUILD_FAILED,
326
+ import_browser4.CaatingaErrorCode.XDR_BUILD_FAILED,
161
327
  "Check the generated binding transaction object.",
162
328
  error
163
329
  );
@@ -166,52 +332,15 @@ async function buildXdr(input) {
166
332
  function readXdr(transaction) {
167
333
  const candidate = transaction;
168
334
  if (typeof candidate.toXDR !== "function") {
169
- throw new import_browser3.CaatingaError(
335
+ throw new import_browser4.CaatingaError(
170
336
  "Binding transaction object does not expose toXDR().",
171
- import_browser3.CaatingaErrorCode.XDR_BUILD_FAILED,
337
+ import_browser4.CaatingaErrorCode.XDR_BUILD_FAILED,
172
338
  "Regenerate bindings or provide a compatible binding adapter."
173
339
  );
174
340
  }
175
341
  return candidate.toXDR();
176
342
  }
177
343
 
178
- // src/wallet/with-wallet-timeout.ts
179
- var import_browser4 = require("@caatinga/core/browser");
180
- function withWalletTimeout(label, timeoutMs, fn) {
181
- if (timeoutMs === void 0 || timeoutMs <= 0) {
182
- return fn();
183
- }
184
- let timedOut = false;
185
- return new Promise((resolve, reject) => {
186
- const timer = setTimeout(() => {
187
- timedOut = true;
188
- reject(
189
- new import_browser4.CaatingaError(
190
- `Wallet "${label}" timed out after ${timeoutMs}ms.`,
191
- import_browser4.CaatingaErrorCode.WALLET_TIMEOUT,
192
- "Ensure the wallet adapter rejects on user dismissal, or increase walletTimeout."
193
- )
194
- );
195
- }, timeoutMs);
196
- fn().then(
197
- (value) => {
198
- if (timedOut) {
199
- return;
200
- }
201
- clearTimeout(timer);
202
- resolve(value);
203
- },
204
- (error) => {
205
- if (timedOut) {
206
- return;
207
- }
208
- clearTimeout(timer);
209
- reject(error);
210
- }
211
- );
212
- });
213
- }
214
-
215
344
  // src/client/invoke-args.ts
216
345
  function splitArgsAndOptions(argsOrOptions, maybeOptions) {
217
346
  return {
@@ -536,8 +665,10 @@ function createCaatingaClient(config) {
536
665
  // Annotate the CommonJS export names for ESM import in node:
537
666
  0 && (module.exports = {
538
667
  CaatingaContractClient,
668
+ WALLET_SESSION_STORAGE_KEY,
539
669
  buildXdr,
540
670
  createCaatingaClient,
541
671
  createDefaultBindingAdapter,
672
+ createWalletSession,
542
673
  resolveContractId
543
674
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { a as CaatingaBindingAdapter, b as CaatingaClientConfig, c as CaatingaContractRegistration, d as CaatingaXdrBuildResult, e as CaatingaInvokeOptions, f as CaatingaInvokeResult, g as CaatingaReadOptions, h as CaatingaReadResult } from './types-D4XEyX4J.cjs';
2
2
  export { i as CaatingaInvokeStatus, j as CaatingaNetwork, C as CaatingaWalletAdapter } from './types-D4XEyX4J.cjs';
3
+ export { C as CaatingaWalletCapabilities, W as WALLET_SESSION_STORAGE_KEY, a as WalletSession, b as WalletSessionOptions, c as WalletSessionState, d as WalletSessionStatus, e as WalletSessionStorage, f as createWalletSession } from './wallet-session-ed2Dgs9A.cjs';
3
4
  import { CaatingaArtifacts } from '@caatinga/core/browser';
4
5
 
5
6
  declare function resolveContractId(input: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { a as CaatingaBindingAdapter, b as CaatingaClientConfig, c as CaatingaContractRegistration, d as CaatingaXdrBuildResult, e as CaatingaInvokeOptions, f as CaatingaInvokeResult, g as CaatingaReadOptions, h as CaatingaReadResult } from './types-D4XEyX4J.js';
2
2
  export { i as CaatingaInvokeStatus, j as CaatingaNetwork, C as CaatingaWalletAdapter } from './types-D4XEyX4J.js';
3
+ export { C as CaatingaWalletCapabilities, W as WALLET_SESSION_STORAGE_KEY, a as WalletSession, b as WalletSessionOptions, c as WalletSessionState, d as WalletSessionStatus, e as WalletSessionStorage, f as createWalletSession } from './wallet-session-DeLp8S1c.js';
3
4
  import { CaatingaArtifacts } from '@caatinga/core/browser';
4
5
 
5
6
  declare function resolveContractId(input: {