@orbi-wallet/sdk 0.0.3 → 0.1.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
@@ -1,8 +1,8 @@
1
1
  # @orbi-wallet/sdk
2
2
 
3
- The official SDK for integrating [Orbi Smart Wallet](https://orbiwallet.xyz) into any Stellar dApp.
3
+ The official SDK for connecting your Stellar dApp to [Orbi Smart Wallet](https://orbiwallet.xyz).
4
4
 
5
- Orbi is a passkey-based smart wallet on Stellar. Users sign transactions with Face ID or Touch ID — no seed phrase, no browser extension. This SDK lets your dApp connect to Orbi wallets and optionally sponsor gas so your users pay nothing.
5
+ Orbi is a passkey-secured **classic Stellar (G) account** wallet. Users sign in and approve transactions with Face ID / Touch ID — no seed phrase, no browser extension.
6
6
 
7
7
  ---
8
8
 
@@ -14,290 +14,90 @@ npm install @orbi-wallet/sdk
14
14
 
15
15
  ---
16
16
 
17
- ## Quick Start
18
-
19
- ### User pays gas (default)
20
-
21
- ```ts
22
- import { OrbiClient } from '@orbi-wallet/sdk';
23
-
24
- const orbi = new OrbiClient({
25
- apiUrl: 'https://api.orbiwallet.xyz',
26
- });
27
- ```
28
-
29
- ### dApp sponsors gas (gasless)
17
+ ## Quick start
30
18
 
31
19
  ```ts
32
20
  import { OrbiClient } from '@orbi-wallet/sdk';
33
21
 
34
- const orbi = new OrbiClient({
35
- apiUrl: 'https://api.orbiwallet.xyz',
36
- apiKey: 'YOUR_API_KEY', // get this from developers.orbiwallet.xyz
37
- });
38
- ```
39
-
40
- That one line is the only difference. Everything else — connecting, signing, submitting — works exactly the same. Users see the fee crossed out and "Sponsored by [your app]".
22
+ const orbi = new OrbiClient({ network: 'testnet' });
41
23
 
42
- ---
24
+ // 1. Connect — opens a popup, resolves once the user approves
25
+ const { walletAddress } = await orbi.connect();
43
26
 
44
- ## How It Works
27
+ // 2. Build a transaction with @stellar/stellar-sdk and get its XDR
28
+ const xdr = transaction.toXDR();
45
29
 
46
- Orbi uses a **redirect flow** no popups, no browser extensions. Works on all devices including mobile.
30
+ // 3. Ask the user to review and sign opens a popup, resolves with the signed XDR
31
+ const { signedXdr } = await orbi.signTransaction({ xdr });
47
32
 
48
- ```
49
- 1. orbi.connect() → redirect user to Orbi to connect wallet
50
- 2. orbi.handleCallback() → on return, get wallet address + passkey ID
51
- 3. orbi.sign() → redirect user to approve transaction with passkey
52
- 4. orbi.handleSignCallback() + orbi.bundle() → submit signed tx to relay
53
- 5. orbi.waitForConfirmation(opId) → wait for on-chain confirmation (~5s)
33
+ // 4. Submit signedXdr to Horizon yourself (e.g. server.submitTransaction)
54
34
  ```
55
35
 
56
36
  ---
57
37
 
58
- ## Full Example
59
-
60
- ### Step 1 — Connect wallet
38
+ ## How it works
61
39
 
62
- On your "Connect" button:
63
-
64
- ```ts
65
- orbi.connect({
66
- redirectUrl: 'https://yourapp.com/callback',
67
- });
68
- // navigates to keys.orbiwallet.xyz, then back to redirectUrl?token=...
69
- ```
70
-
71
- On your `/callback` page:
72
-
73
- ```ts
74
- const wallet = await orbi.handleCallback();
75
- if (wallet) {
76
- // { walletAddress, passkeyId, email }
77
- localStorage.setItem('walletAddress', wallet.walletAddress);
78
- }
79
- ```
80
-
81
- ### Step 2 — Sign and submit a transaction
82
-
83
- ```ts
84
- // Redirect user to approve
85
- orbi.sign({
86
- walletAddress: localStorage.getItem('walletAddress')!,
87
- contractId: 'YOUR_CONTRACT_ID',
88
- functionName: 'transfer',
89
- argsXdr: ['...', '...'], // XDR-encoded args
90
- redirectUrl: 'https://yourapp.com/sign-callback',
91
- });
92
- ```
40
+ Orbi uses a **popup + postMessage** handshake — the same integration pattern Coinbase Smart Wallet and most modern wallet SDKs use:
93
41
 
94
- On your `/sign-callback` page:
42
+ 1. `orbi.connect()` / `orbi.signTransaction()` opens `keys.orbiwallet.xyz` in a popup
43
+ 2. The user approves with their passkey (signs in, or creates a wallet first if new)
44
+ 3. The popup posts the result back to your page and closes itself
45
+ 4. The returned promise resolves with the result
95
46
 
96
- ```ts
97
- const result = orbi.handleSignCallback();
98
- if (!result) return; // user cancelled or came directly to this page
99
-
100
- const { opId } = await orbi.bundle({
101
- walletAddress: result.walletAddress,
102
- quoteId: result.quoteId,
103
- signedAuthEntryXdr: result.signedAuthEntryXdr,
104
- contractId: 'YOUR_CONTRACT_ID',
105
- functionName: 'transfer',
106
- argsXdr: result.argsXdr,
107
- });
108
-
109
- // Wait for on-chain confirmation via SSE
110
- const status = await orbi.waitForConfirmation(opId);
111
- if (status.status === 'confirmed') {
112
- console.log('tx hash:', status.txHash);
113
- }
114
- ```
47
+ The connected wallet is cached in `localStorage`, so later `connect()` calls resolve instantly without reopening the popup. Call `disconnect()` to clear it.
115
48
 
116
49
  ---
117
50
 
118
- ## API Reference
51
+ ## API
119
52
 
120
- ### `new OrbiClient(config)`
53
+ ### `new OrbiClient(config?)`
121
54
 
122
- | Parameter | Type | Required | Description |
55
+ | Option | Type | Default | Description |
123
56
  |---|---|---|---|
124
- | `apiUrl` | `string` | Yes | Orbi relay URL use `https://api.orbiwallet.xyz` |
125
- | `apiKey` | `string` | No | Your developer API key. Enables gas sponsorship. |
126
-
127
- ---
128
-
129
- ### Wallet Connection
130
-
131
- #### `connect(params)`
132
-
133
- Redirects the user to Orbi to connect their wallet.
134
-
135
- ```ts
136
- orbi.connect({ redirectUrl: 'https://yourapp.com/callback' });
137
- ```
138
-
139
- #### `handleCallback()`
140
-
141
- Call on your callback page after `connect()` redirects back. Returns wallet data or `null` if no token in URL.
142
-
143
- ```ts
144
- const wallet = await orbi.handleCallback();
145
- // { walletAddress: string, passkeyId: string, email: string } | null
146
- ```
147
-
148
- ---
149
-
150
- ### Transaction Signing
151
-
152
- #### `sign(params)`
153
-
154
- Redirects the user to Orbi to approve a transaction with their passkey.
155
-
156
- ```ts
157
- orbi.sign({
158
- walletAddress: string,
159
- contractId: string,
160
- functionName: string,
161
- argsXdr: string[], // XDR-encoded Soroban args
162
- redirectUrl: string,
163
- });
164
- ```
57
+ | `network` | `'testnet' \| 'mainnet'` | `'testnet'` | Stellar network — passed through so Orbi shows the right network badge and validates the transaction against the matching ledger |
165
58
 
166
- If `apiKey` is set, the user sees the fee as sponsored — they pay nothing.
59
+ ### `connect(): Promise<ConnectedWallet>`
167
60
 
168
- #### `handleSignCallback()`
169
-
170
- Call on your sign-callback page. Returns the signed data needed to call `bundle()`, or `null` if nothing to process.
171
-
172
- ```ts
173
- const result = orbi.handleSignCallback();
174
- // { signedAuthEntryXdr, quoteId, argsXdr, nativeSacId, walletAddress } | null
175
- ```
176
-
177
- ---
178
-
179
- ### Relay
180
-
181
- #### `bundle(params)`
182
-
183
- Submit a signed operation to the Orbi relay for on-chain execution.
184
-
185
- ```ts
186
- const { opId } = await orbi.bundle({
187
- walletAddress: string,
188
- quoteId: string,
189
- signedAuthEntryXdr: string,
190
- contractId: string,
191
- functionName: string,
192
- argsXdr: string[],
193
- });
194
- ```
195
-
196
- #### `waitForConfirmation(opId)`
197
-
198
- Wait for the operation to be confirmed or fail. Uses SSE — resolves in ~5s on Stellar.
199
-
200
- ```ts
201
- const status = await orbi.waitForConfirmation(opId);
202
- // { opId, status: 'confirmed' | 'failed', txHash: string | null, error: string | null }
203
- ```
204
-
205
- #### `getStatus(opId)`
206
-
207
- One-shot status check (non-blocking alternative to `waitForConfirmation`).
208
-
209
- ```ts
210
- const status = await orbi.getStatus(opId);
211
- ```
212
-
213
- ---
214
-
215
- ### Token Management
216
-
217
- #### `watchAsset(params)`
218
-
219
- Redirect the user to add a Soroban token to their Orbi wallet.
220
-
221
- ```ts
222
- orbi.watchAsset({
223
- contractId: 'TOKEN_CONTRACT_ID',
224
- redirectUrl: 'https://yourapp.com/callback',
225
- });
226
- ```
227
-
228
- #### `handleWatchAssetCallback()`
229
-
230
- Call on your callback page after `watchAsset()` redirects back.
61
+ Connects the user's wallet. Returns a cached session instantly if one exists; otherwise opens the connect popup and resolves once the user approves.
231
62
 
232
63
  ```ts
233
- const result = orbi.handleWatchAssetCallback();
234
- // { contractId: string, added: boolean } | null
64
+ const { walletAddress, credentialId, passkeyId } = await orbi.connect();
235
65
  ```
236
66
 
237
- ---
238
-
239
- ### Gas Sponsorship (Developer Setup)
240
-
241
- These methods are for onboarding your dApp to gas sponsorship. You can also do this through the [developer portal](https://developers.orbiwallet.xyz).
67
+ ### `signTransaction({ xdr, walletAddress? }): Promise<SignResult>`
242
68
 
243
- #### `register(params)`
69
+ Opens the sign popup so the user can review and approve a transaction with their passkey.
244
70
 
245
- Register a new developer account. Returns a one-time API key save it immediately.
71
+ - `xdr` base64-encoded `TransactionEnvelope` XDR for a classic G account (build it with `@stellar/stellar-sdk`)
72
+ - `walletAddress` — optional; defaults to the currently connected address
246
73
 
247
74
  ```ts
248
- const { apiKey } = await orbi.register({
249
- developerName: 'My App',
250
- email: 'you@example.com',
251
- });
75
+ const { signedXdr, walletAddress } = await orbi.signTransaction({ xdr });
252
76
  ```
253
77
 
254
- #### `setDeployer(deployerPublicKey)`
255
-
256
- Set the Stellar G... address that will fund gas for your users. Requires `apiKey` in constructor.
257
-
258
- ```ts
259
- await orbi.setDeployer('GABC...XYZ');
260
- ```
261
-
262
- #### `getDeployerBalance()`
263
-
264
- Check your gas tank balance. Requires `apiKey` in constructor.
265
-
266
- ```ts
267
- const { balanceXlm } = await orbi.getDeployerBalance();
268
- console.log(`${balanceXlm} XLM remaining`);
269
- ```
270
-
271
- ---
78
+ Orbi only signs — it does not submit. Submit `signedXdr` to Horizon yourself.
272
79
 
273
- ## Gas Sponsorship Setup
80
+ ### `getAddress(): string | null`
274
81
 
275
- To sponsor gas for your users:
82
+ Returns the cached wallet address from a previous `connect()`, or `null` if not connected.
276
83
 
277
- 1. **Create a developer account** at [developers.orbiwallet.xyz](https://developers.orbiwallet.xyz) — get your API key
278
- 2. **Create a Stellar keypair** for your gas tank (a regular G... account)
279
- 3. **Fund it with XLM** — each sponsored transaction deducts the exact network fee
280
- 4. **Set the deployer** in the developer portal (or via `setDeployer()`)
281
- 5. **Add `apiKey`** to your `OrbiClient` constructor
84
+ ### `disconnect(): void`
282
85
 
283
- That's it. Users with Orbi wallets will see fees as sponsored. Users on other wallets are unaffected.
86
+ Clears the cached session on your side. Doesn't revoke anything on Orbi the user can reconnect any time with their passkey.
284
87
 
285
88
  ---
286
89
 
287
90
  ## Notes
288
91
 
289
- - Works on all browsers and mobileredirect flow, no popups or extensions
290
- - Only works with Orbi smart wallets other Stellar wallets (Freighter, Lobstr) are unaffected
291
- - Stellar ledger closes every ~5s, so `waitForConfirmation` typically resolves in under 10s
292
- - XDR arg encoding: use `@stellar/stellar-sdk` to build and encode Soroban contract args
92
+ - Requires popups to be allowed for your site `connect()`/`signTransaction()` reject with a clear error if the popup is blocked or the user closes it
93
+ - Works with Orbi G-wallets only (classic Stellar accounts)
94
+ - Results are only ever accepted from `https://keys.orbiwallet.xyz`, verified by both message origin and source window — never trust `postMessage` from elsewhere
293
95
 
294
96
  ---
295
97
 
296
98
  ## Links
297
99
 
298
100
  - [Orbi Wallet](https://orbiwallet.xyz)
299
- - [Developer Portal](https://developers.orbiwallet.xyz)
300
- - [npm](https://npmjs.com/package/@orbi-wallet/sdk)
301
101
  - [GitHub](https://github.com/Novablitz404/orbi-smart-wallet)
302
102
 
303
103
  ---
package/dist/client.d.ts CHANGED
@@ -1,99 +1,38 @@
1
1
  /**
2
- * @orbi-wallet/sdk — Orbi Smart Wallet SDK
2
+ * @orbi-wallet/sdk — Orbi Smart Wallet SDK (G-wallet)
3
3
  *
4
- * Lets any Stellar dApp integrate Orbi passkey wallets using a redirect flow.
5
- * Works on all devices and browsers no popups, no extensions needed.
4
+ * Connects a Stellar dApp to Orbi passkey wallets via a popup + postMessage
5
+ * handshake the same integration pattern Coinbase Smart Wallet uses.
6
6
  *
7
- * Quick start (user pays gas):
8
- * const orbi = new OrbiClient({ apiUrl: 'https://api.orbiwallet.xyz' });
9
- *
10
- * Quick start (dApp sponsors gas):
11
- * const orbi = new OrbiClient({
12
- * apiUrl: 'https://api.orbiwallet.xyz',
13
- * apiKey: 'YOUR_API_KEY', // <-- enables gasless
14
- * });
15
- *
16
- * Flow:
17
- * 1. orbi.connect({ redirectUrl }) — redirect user to connect wallet
18
- * 2. orbi.handleCallback() — on return, exchange token for wallet data
19
- * 3. orbi.sign({ ..., redirectUrl }) — redirect user to approve transaction
20
- * 4. orbi.handleSignCallback() → bundle() — on return, submit signed tx
21
- * 5. orbi.waitForConfirmation(opId) — wait for on-chain confirmation
7
+ * const orbi = new OrbiClient();
8
+ * const { walletAddress } = await orbi.connect();
9
+ * const { signedXdr } = await orbi.signTransaction({ xdr });
22
10
  */
23
- import type { OrbiClientConfig, OpStatus, DeployerBalance } from './types';
11
+ import type { ConnectedWallet, OrbiClientConfig, SignResult } from './types';
24
12
  export declare class OrbiClient {
25
- private apiUrl;
26
- private apiKey?;
27
- constructor(config: OrbiClientConfig);
28
- private authHeaders;
29
- /** Redirect the user to Orbi to connect their wallet. */
30
- connect(params: {
31
- redirectUrl: string;
32
- }): void;
33
- /** Call this on your callback page after orbi.connect() redirects back. */
34
- handleCallback(): Promise<{
35
- walletAddress: string;
36
- passkeyId: string;
37
- email: string;
38
- } | null>;
13
+ private network;
14
+ constructor(config?: OrbiClientConfig);
15
+ /** Wallet address from a previous `connect()`, restored from local storage. */
16
+ getAddress(): string | null;
17
+ /** Forget the cached session. The next `connect()` reopens the Orbi popup. */
18
+ disconnect(): void;
19
+ private getSession;
39
20
  /**
40
- * Redirect the user to Orbi to approve a transaction with their passkey.
41
- * If apiKey is set, the user will see the fee as sponsored — they pay nothing.
21
+ * Connect the user's Orbi wallet. Resolves immediately from a cached
22
+ * session if one exists; otherwise opens the Orbi connect popup and
23
+ * resolves once the user approves.
42
24
  */
43
- sign(params: {
44
- walletAddress: string;
45
- contractId: string;
46
- functionName: string;
47
- argsXdr: string[];
48
- redirectUrl: string;
49
- }): void;
50
- /** Call this on your sign-callback page after orbi.sign() redirects back. */
51
- handleSignCallback(): {
52
- signedAuthEntryXdr: string;
53
- quoteId: string;
54
- argsXdr: string[];
55
- nativeSacId: string;
56
- walletAddress: string;
57
- } | null;
58
- /** Redirect the user to add a token to their Orbi wallet. */
59
- watchAsset(params: {
60
- contractId: string;
61
- redirectUrl: string;
62
- }): void;
63
- /** Call this on your callback page after orbi.watchAsset() redirects back. */
64
- handleWatchAssetCallback(): {
65
- contractId: string;
66
- added: boolean;
67
- } | null;
25
+ connect(): Promise<ConnectedWallet>;
68
26
  /**
69
- * Submit a signed operation to the Orbi relay.
70
- * If apiKey is set and a deployer is configured, gas is sponsored automatically.
27
+ * Ask the user to review and approve a transaction with their passkey.
28
+ *
29
+ * `xdr` is a base64-encoded TransactionEnvelope for a classic G account —
30
+ * build it with `@stellar/stellar-sdk` on your end. Orbi only signs; submit
31
+ * the returned `signedXdr` to Horizon yourself.
71
32
  */
72
- bundle(params: {
73
- walletAddress: string;
74
- quoteId: string;
75
- signedAuthEntryXdr: string;
76
- contractId: string;
77
- functionName: string;
78
- argsXdr: string[];
79
- }): Promise<{
80
- opId: string;
81
- }>;
82
- /** One-shot status check for an operation. */
83
- getStatus(opId: string): Promise<OpStatus>;
84
- /** Wait for confirmed or failed via SSE (~5s on Stellar). */
85
- waitForConfirmation(opId: string): Promise<OpStatus>;
86
- /** Register a new developer account. Returns a one-time API key — save it. */
87
- register(params: {
88
- developerName: string;
89
- email: string;
90
- deployerPublicKey?: string;
91
- }): Promise<{
92
- apiKey: string;
93
- message: string;
94
- }>;
95
- /** Set or update the deployer (gas tank) address for this API key. */
96
- setDeployer(deployerPublicKey: string): Promise<void>;
97
- /** Check the XLM balance of your gas tank. */
98
- getDeployerBalance(): Promise<DeployerBalance>;
33
+ signTransaction(params: {
34
+ xdr: string;
35
+ walletAddress?: string;
36
+ }): Promise<SignResult>;
37
+ private openPopup;
99
38
  }
package/dist/client.js CHANGED
@@ -1,237 +1,142 @@
1
1
  "use strict";
2
2
  /**
3
- * @orbi-wallet/sdk — Orbi Smart Wallet SDK
3
+ * @orbi-wallet/sdk — Orbi Smart Wallet SDK (G-wallet)
4
4
  *
5
- * Lets any Stellar dApp integrate Orbi passkey wallets using a redirect flow.
6
- * Works on all devices and browsers no popups, no extensions needed.
5
+ * Connects a Stellar dApp to Orbi passkey wallets via a popup + postMessage
6
+ * handshake the same integration pattern Coinbase Smart Wallet uses.
7
7
  *
8
- * Quick start (user pays gas):
9
- * const orbi = new OrbiClient({ apiUrl: 'https://api.orbiwallet.xyz' });
10
- *
11
- * Quick start (dApp sponsors gas):
12
- * const orbi = new OrbiClient({
13
- * apiUrl: 'https://api.orbiwallet.xyz',
14
- * apiKey: 'YOUR_API_KEY', // <-- enables gasless
15
- * });
16
- *
17
- * Flow:
18
- * 1. orbi.connect({ redirectUrl }) — redirect user to connect wallet
19
- * 2. orbi.handleCallback() — on return, exchange token for wallet data
20
- * 3. orbi.sign({ ..., redirectUrl }) — redirect user to approve transaction
21
- * 4. orbi.handleSignCallback() → bundle() — on return, submit signed tx
22
- * 5. orbi.waitForConfirmation(opId) — wait for on-chain confirmation
8
+ * const orbi = new OrbiClient();
9
+ * const { walletAddress } = await orbi.connect();
10
+ * const { signedXdr } = await orbi.signTransaction({ xdr });
23
11
  */
24
12
  Object.defineProperty(exports, "__esModule", { value: true });
25
13
  exports.OrbiClient = void 0;
14
+ // Generated from package.json at build time (see scripts/generate-version.js)
15
+ // — package.json stays the single source of truth for name/version.
16
+ const version_1 = require("./version");
26
17
  const KEYS_URL = 'https://keys.orbiwallet.xyz';
27
- function _patchBuffer() {
28
- if (typeof window === 'undefined')
29
- return;
30
- function dv(b) { return new DataView(b.buffer, b.byteOffset, b.byteLength); }
31
- function def(p, n, fn) {
32
- if (typeof p[n] !== 'function')
33
- Object.defineProperty(p, n, { value: fn, writable: true, configurable: true });
34
- }
35
- function patch(p) {
36
- if (!p)
37
- return;
38
- def(p, 'readBigInt64BE', function (o = 0) { return dv(this).getBigInt64(o, false); });
39
- def(p, 'readBigUInt64BE', function (o = 0) { return dv(this).getBigUint64(o, false); });
40
- def(p, 'readBigInt64LE', function (o = 0) { return dv(this).getBigInt64(o, true); });
41
- def(p, 'readBigUInt64LE', function (o = 0) { return dv(this).getBigUint64(o, true); });
42
- def(p, 'writeBigInt64BE', function (v, o = 0) { dv(this).setBigInt64(o, v, false); return o + 8; });
43
- def(p, 'writeBigUInt64BE', function (v, o = 0) { dv(this).setBigUint64(o, v, false); return o + 8; });
44
- def(p, 'writeBigInt64LE', function (v, o = 0) { dv(this).setBigInt64(o, v, true); return o + 8; });
45
- def(p, 'writeBigUInt64LE', function (v, o = 0) { dv(this).setBigUint64(o, v, true); return o + 8; });
46
- }
47
- patch(Uint8Array.prototype);
48
- patch(globalThis.Buffer?.prototype);
49
- }
18
+ const KEYS_ORIGIN = new URL(KEYS_URL).origin;
19
+ const SESSION_KEY = 'orbi_session';
20
+ const POPUP_WIDTH = 420;
21
+ const POPUP_HEIGHT = 640;
50
22
  class OrbiClient {
51
- constructor(config) {
52
- this.apiUrl = config.apiUrl.replace(/\/$/, '');
53
- this.apiKey = config.apiKey;
54
- _patchBuffer();
23
+ constructor(config = {}) {
24
+ this.network = config.network ?? 'testnet';
55
25
  }
56
- authHeaders() {
57
- return this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {};
26
+ // ── Session ─────────────────────────────────────────────────────────────────
27
+ /** Wallet address from a previous `connect()`, restored from local storage. */
28
+ getAddress() {
29
+ return this.getSession()?.walletAddress ?? null;
58
30
  }
59
- // ── Wallet connection ───────────────────────────────────────────────────────
60
- /** Redirect the user to Orbi to connect their wallet. */
61
- connect(params) {
62
- const url = new URL(`${KEYS_URL}/connect`);
63
- url.searchParams.set('redirect', params.redirectUrl);
64
- url.searchParams.set('origin', window.location.origin);
65
- window.location.href = url.toString();
31
+ /** Forget the cached session. The next `connect()` reopens the Orbi popup. */
32
+ disconnect() {
33
+ if (typeof window === 'undefined')
34
+ return;
35
+ localStorage.removeItem(SESSION_KEY);
66
36
  }
67
- /** Call this on your callback page after orbi.connect() redirects back. */
68
- async handleCallback() {
69
- const params = new URLSearchParams(window.location.search);
70
- const token = params.get('token');
71
- if (!token)
37
+ getSession() {
38
+ if (typeof window === 'undefined')
72
39
  return null;
73
- const res = await fetch(`${this.apiUrl}/v1/auth/tokens/${token}`);
74
- if (!res.ok)
75
- throw new Error('Invalid or expired Orbi token');
76
- return res.json();
40
+ const raw = localStorage.getItem(SESSION_KEY);
41
+ return raw ? JSON.parse(raw) : null;
77
42
  }
78
- // ── Transaction signing ─────────────────────────────────────────────────────
43
+ // ── Wallet connection ───────────────────────────────────────────────────────
79
44
  /**
80
- * Redirect the user to Orbi to approve a transaction with their passkey.
81
- * If apiKey is set, the user will see the fee as sponsored — they pay nothing.
45
+ * Connect the user's Orbi wallet. Resolves immediately from a cached
46
+ * session if one exists; otherwise opens the Orbi connect popup and
47
+ * resolves once the user approves.
82
48
  */
83
- sign(params) {
84
- const url = new URL(`${KEYS_URL}/sign`);
85
- url.searchParams.set('redirect', params.redirectUrl);
86
- url.searchParams.set('origin', window.location.origin);
87
- url.searchParams.set('walletAddress', params.walletAddress);
88
- url.searchParams.set('contractId', params.contractId);
89
- url.searchParams.set('functionName', params.functionName);
90
- url.searchParams.set('argsXdr', JSON.stringify(params.argsXdr));
91
- if (this.apiKey)
92
- url.searchParams.set('apiKey', this.apiKey);
93
- window.location.href = url.toString();
94
- }
95
- /** Call this on your sign-callback page after orbi.sign() redirects back. */
96
- handleSignCallback() {
97
- const params = new URLSearchParams(window.location.search);
98
- const signedXdr = params.get('signedXdr');
99
- const quoteId = params.get('quoteId');
100
- const argsXdrRaw = params.get('argsXdr');
101
- const nativeSacId = params.get('nativeSacId');
102
- const walletAddress = params.get('walletAddress');
103
- if (!signedXdr || !quoteId || !argsXdrRaw || !nativeSacId || !walletAddress)
104
- return null;
105
- return {
106
- signedAuthEntryXdr: signedXdr,
107
- quoteId,
108
- argsXdr: JSON.parse(argsXdrRaw),
109
- nativeSacId,
110
- walletAddress,
111
- };
112
- }
113
- // ── Token management ────────────────────────────────────────────────────────
114
- /** Redirect the user to add a token to their Orbi wallet. */
115
- watchAsset(params) {
116
- const url = new URL(`${KEYS_URL}/watch-asset`);
117
- url.searchParams.set('contractId', params.contractId);
118
- url.searchParams.set('redirect', params.redirectUrl);
119
- url.searchParams.set('origin', window.location.origin);
120
- window.location.href = url.toString();
121
- }
122
- /** Call this on your callback page after orbi.watchAsset() redirects back. */
123
- handleWatchAssetCallback() {
124
- const params = new URLSearchParams(window.location.search);
125
- const contractId = params.get('watchedContractId');
126
- if (!contractId)
127
- return null;
128
- return { contractId, added: params.get('watched') === 'true' };
49
+ async connect() {
50
+ const cached = this.getSession();
51
+ if (cached)
52
+ return cached;
53
+ const wallet = await this.openPopup({
54
+ path: '/connect',
55
+ successType: 'orbi_connected',
56
+ mapSuccess: (msg) => ({
57
+ walletAddress: msg.address,
58
+ credentialId: msg.credentialId ?? '',
59
+ passkeyId: msg.passkeyId ?? '',
60
+ }),
61
+ });
62
+ localStorage.setItem(SESSION_KEY, JSON.stringify(wallet));
63
+ return wallet;
129
64
  }
130
- // ── Relay API ───────────────────────────────────────────────────────────────
65
+ // ── Transaction signing ─────────────────────────────────────────────────────
131
66
  /**
132
- * Submit a signed operation to the Orbi relay.
133
- * If apiKey is set and a deployer is configured, gas is sponsored automatically.
67
+ * Ask the user to review and approve a transaction with their passkey.
68
+ *
69
+ * `xdr` is a base64-encoded TransactionEnvelope for a classic G account —
70
+ * build it with `@stellar/stellar-sdk` on your end. Orbi only signs; submit
71
+ * the returned `signedXdr` to Horizon yourself.
134
72
  */
135
- async bundle(params) {
136
- const res = await fetch(`${this.apiUrl}/v1/bundle`, {
137
- method: 'POST',
138
- headers: {
139
- 'Content-Type': 'application/json',
140
- ...this.authHeaders(),
73
+ signTransaction(params) {
74
+ const walletAddress = params.walletAddress ?? this.getAddress() ?? undefined;
75
+ return this.openPopup({
76
+ path: '/sign',
77
+ params: {
78
+ xdr: params.xdr,
79
+ network: this.network,
80
+ ...(walletAddress ? { walletAddress } : {}),
141
81
  },
142
- body: JSON.stringify({
143
- walletAddress: params.walletAddress,
144
- quoteId: params.quoteId,
145
- authEntryXdr: params.signedAuthEntryXdr,
146
- call: {
147
- contractId: params.contractId,
148
- function: params.functionName,
149
- argsXdr: params.argsXdr,
150
- },
82
+ successType: 'orbi_g_signed',
83
+ mapSuccess: (msg) => ({
84
+ signedXdr: msg.signedXdr,
85
+ walletAddress: msg.walletAddress,
151
86
  }),
152
87
  });
153
- if (!res.ok) {
154
- const err = await res.json().catch(() => ({}));
155
- throw new Error(err.error ?? `Bundle failed: ${res.status}`);
156
- }
157
- return res.json();
158
88
  }
159
- /** One-shot status check for an operation. */
160
- async getStatus(opId) {
161
- const res = await fetch(`${this.apiUrl}/v1/status/${opId}`);
162
- if (!res.ok)
163
- throw new Error(`Status check failed: ${res.status}`);
164
- return res.json();
165
- }
166
- /** Wait for confirmed or failed via SSE (~5s on Stellar). */
167
- waitForConfirmation(opId) {
89
+ // ── Popup + postMessage handshake ───────────────────────────────────────────
90
+ openPopup(req) {
91
+ if (typeof window === 'undefined') {
92
+ return Promise.reject(new Error('Orbi requires a browser environment'));
93
+ }
94
+ const url = new URL(`${KEYS_URL}${req.path}`);
95
+ url.searchParams.set('origin', window.location.origin);
96
+ url.searchParams.set('sdkName', version_1.SDK_NAME);
97
+ url.searchParams.set('sdkVersion', version_1.SDK_VERSION);
98
+ for (const [key, value] of Object.entries(req.params ?? {}))
99
+ url.searchParams.set(key, value);
100
+ const left = window.screenX + Math.max(0, (window.outerWidth - POPUP_WIDTH) / 2);
101
+ const top = window.screenY + Math.max(0, (window.outerHeight - POPUP_HEIGHT) / 2);
102
+ const popup = window.open(url.toString(), 'orbi-wallet', `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top}`);
103
+ if (!popup) {
104
+ return Promise.reject(new Error('Popup blocked — allow popups for this site to use Orbi'));
105
+ }
168
106
  return new Promise((resolve, reject) => {
169
- const es = new EventSource(`${this.apiUrl}/v1/wallet/events/${opId}`);
170
- es.onmessage = (e) => {
171
- try {
172
- const data = JSON.parse(e.data);
173
- es.close();
174
- if (data.status === 'confirmed') {
175
- resolve({ opId, status: 'confirmed', txHash: data.txHash ?? null, error: null });
176
- }
177
- else if (data.status === 'failed') {
178
- resolve({ opId, status: 'failed', txHash: null, error: data.error ?? 'Transaction failed' });
179
- }
180
- else if (data.status === 'timeout') {
181
- reject(new Error(`Op ${opId} timed out`));
182
- }
107
+ let settled = false;
108
+ const settle = (fn) => {
109
+ if (settled)
110
+ return;
111
+ settled = true;
112
+ window.removeEventListener('message', onMessage);
113
+ clearInterval(closedCheck);
114
+ fn();
115
+ };
116
+ const onMessage = (event) => {
117
+ // event.origin is never affected by Cross-Origin-Opener-Policy and
118
+ // can't be spoofed — always required. event.source (the popup window
119
+ // reference) adds extra anti-spoofing when the browser provides it,
120
+ // but COOP isolation can null it out on either side, so we only
121
+ // enforce it when present rather than rejecting legit messages.
122
+ if (event.origin !== KEYS_ORIGIN)
123
+ return;
124
+ if (event.source !== null && event.source !== popup)
125
+ return;
126
+ const data = event.data;
127
+ if (data?.type === req.successType) {
128
+ settle(() => resolve(req.mapSuccess(data)));
183
129
  }
184
- catch {
185
- es.close();
186
- reject(new Error('Invalid SSE response'));
130
+ else if (data?.type === 'orbi_cancelled') {
131
+ settle(() => reject(new Error('Cancelled in Orbi')));
187
132
  }
188
133
  };
189
- es.onerror = () => {
190
- es.close();
191
- reject(new Error(`Lost connection waiting for op ${opId}`));
192
- };
134
+ const closedCheck = setInterval(() => {
135
+ if (popup.closed)
136
+ settle(() => reject(new Error('Orbi window closed before completing')));
137
+ }, 500);
138
+ window.addEventListener('message', onMessage);
193
139
  });
194
140
  }
195
- // ── Gas sponsorship onboarding ──────────────────────────────────────────────
196
- /** Register a new developer account. Returns a one-time API key — save it. */
197
- async register(params) {
198
- const res = await fetch(`${this.apiUrl}/v1/account/register`, {
199
- method: 'POST',
200
- headers: { 'Content-Type': 'application/json' },
201
- body: JSON.stringify(params),
202
- });
203
- if (!res.ok) {
204
- const err = await res.json().catch(() => ({}));
205
- throw new Error(err.error ?? `Registration failed: ${res.status}`);
206
- }
207
- return res.json();
208
- }
209
- /** Set or update the deployer (gas tank) address for this API key. */
210
- async setDeployer(deployerPublicKey) {
211
- if (!this.apiKey)
212
- throw new Error('apiKey required — pass it in the OrbiClient constructor');
213
- const res = await fetch(`${this.apiUrl}/v1/account/deployer`, {
214
- method: 'PATCH',
215
- headers: { 'Content-Type': 'application/json', ...this.authHeaders() },
216
- body: JSON.stringify({ deployerPublicKey }),
217
- });
218
- if (!res.ok) {
219
- const err = await res.json().catch(() => ({}));
220
- throw new Error(err.error ?? `Set deployer failed: ${res.status}`);
221
- }
222
- }
223
- /** Check the XLM balance of your gas tank. */
224
- async getDeployerBalance() {
225
- if (!this.apiKey)
226
- throw new Error('apiKey required — pass it in the OrbiClient constructor');
227
- const res = await fetch(`${this.apiUrl}/v1/account/balance`, {
228
- headers: this.authHeaders(),
229
- });
230
- if (!res.ok) {
231
- const err = await res.json().catch(() => ({}));
232
- throw new Error(err.error ?? `Balance fetch failed: ${res.status}`);
233
- }
234
- return res.json();
235
- }
236
141
  }
237
142
  exports.OrbiClient = OrbiClient;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export { OrbiClient } from './client';
2
- export { deriveWalletAddress } from './wallet';
3
- export type { OrbiClientConfig, QuoteResult, BundleResult, OpStatus, OrbiNetwork, DeployerBalance } from './types';
2
+ export type { ConnectedWallet, OrbiClientConfig, OrbiNetwork, SignResult } from './types';
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deriveWalletAddress = exports.OrbiClient = void 0;
3
+ exports.OrbiClient = void 0;
4
4
  var client_1 = require("./client");
5
5
  Object.defineProperty(exports, "OrbiClient", { enumerable: true, get: function () { return client_1.OrbiClient; } });
6
- var wallet_1 = require("./wallet");
7
- Object.defineProperty(exports, "deriveWalletAddress", { enumerable: true, get: function () { return wallet_1.deriveWalletAddress; } });
package/dist/types.d.ts CHANGED
@@ -1,32 +1,14 @@
1
1
  export type OrbiNetwork = 'testnet' | 'mainnet';
2
2
  export interface OrbiClientConfig {
3
- /** Orbi relay API URL https://api.orbiwallet.xyz */
4
- apiUrl: string;
5
- /** Optional: your API key if required by the relay */
6
- apiKey?: string;
3
+ /** Stellar network the dApp is operating on. Defaults to 'testnet'. */
7
4
  network?: OrbiNetwork;
8
5
  }
9
- export interface QuoteResult {
10
- quoteId: string;
11
- feeStroops: number;
12
- feeXlm: string;
13
- expiresAtLedger: number;
14
- currentLedger: number;
15
- nativeSacId: string;
16
- feeCollectorAddress: string;
17
- authEntryXdr: string;
6
+ export interface ConnectedWallet {
7
+ walletAddress: string;
8
+ credentialId: string;
9
+ passkeyId: string;
18
10
  }
19
- export interface BundleResult {
20
- opId: string;
21
- }
22
- export interface OpStatus {
23
- opId: string;
24
- status: 'pending' | 'batched' | 'confirmed' | 'failed';
25
- txHash: string | null;
26
- error: string | null;
27
- }
28
- export interface DeployerBalance {
29
- address: string;
30
- balanceXlm: string;
31
- balanceStroops: string;
11
+ export interface SignResult {
12
+ signedXdr: string;
13
+ walletAddress: string;
32
14
  }
@@ -0,0 +1,2 @@
1
+ export declare const SDK_NAME = "@orbi-wallet/sdk";
2
+ export declare const SDK_VERSION = "0.1.0";
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SDK_VERSION = exports.SDK_NAME = void 0;
4
+ // Generated by scripts/generate-version.js from package.json — do not edit.
5
+ exports.SDK_NAME = "@orbi-wallet/sdk";
6
+ exports.SDK_VERSION = "0.1.0";
package/package.json CHANGED
@@ -1,32 +1,35 @@
1
1
  {
2
2
  "name": "@orbi-wallet/sdk",
3
- "version": "0.0.3",
4
- "description": "Orbi Smart Wallet SDK — connect any dApp to Orbi passkey wallets with optional gasless transactions",
3
+ "version": "0.1.0",
4
+ "description": "Orbi Smart Wallet SDK — connect any Stellar dApp to Orbi passkey wallets",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "files": ["dist"],
7
+ "files": [
8
+ "dist"
9
+ ],
8
10
  "license": "MIT",
9
- "keywords": ["stellar", "smart-wallet", "passkey", "gasless", "soroban", "sdk"],
11
+ "keywords": [
12
+ "stellar",
13
+ "wallet",
14
+ "passkey",
15
+ "sdk"
16
+ ],
10
17
  "homepage": "https://orbiwallet.xyz",
11
18
  "repository": {
12
19
  "type": "git",
13
- "url": "https://github.com/Novablitz404/orbi-smart-wallet"
20
+ "url": "git+https://github.com/Novablitz404/orbi-wallet-sdk.git"
14
21
  },
15
22
  "bugs": {
16
- "url": "https://github.com/Novablitz404/orbi-smart-wallet/issues"
23
+ "url": "https://github.com/Novablitz404/orbi-wallet-sdk/issues"
17
24
  },
18
25
  "scripts": {
26
+ "prebuild": "node scripts/generate-version.js",
19
27
  "build": "tsc",
28
+ "predev": "node scripts/generate-version.js",
20
29
  "dev": "tsc --watch"
21
30
  },
22
- "dependencies": {
23
- "@stellar/stellar-sdk": "^15.1.0"
24
- },
25
31
  "devDependencies": {
26
32
  "@types/node": "^20.14.2",
27
33
  "typescript": "^5.5.2"
28
- },
29
- "peerDependencies": {
30
- "@stellar/stellar-sdk": "^15.1.0"
31
34
  }
32
35
  }
package/dist/wallet.d.ts DELETED
@@ -1,16 +0,0 @@
1
- export type OrbiNetwork = 'testnet' | 'mainnet';
2
- /**
3
- * Derive the deterministic wallet C-address for a passkey ID.
4
- *
5
- * Matches the relay's deriveWalletAddress — same formula:
6
- * salt = sha256(passkey_id)
7
- * address = sha256(networkId + deployer_G_address + salt)
8
- *
9
- * Call this immediately after passkey creation to show the user their
10
- * address — no transaction or network call needed.
11
- */
12
- export declare function deriveWalletAddress(params: {
13
- passkeyId: Uint8Array;
14
- deployerPublicKey: string;
15
- network?: OrbiNetwork;
16
- }): string;
package/dist/wallet.js DELETED
@@ -1,33 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deriveWalletAddress = deriveWalletAddress;
4
- const stellar_sdk_1 = require("@stellar/stellar-sdk");
5
- const PASSPHRASES = {
6
- testnet: stellar_sdk_1.Networks.TESTNET,
7
- mainnet: stellar_sdk_1.Networks.PUBLIC,
8
- };
9
- /**
10
- * Derive the deterministic wallet C-address for a passkey ID.
11
- *
12
- * Matches the relay's deriveWalletAddress — same formula:
13
- * salt = sha256(passkey_id)
14
- * address = sha256(networkId + deployer_G_address + salt)
15
- *
16
- * Call this immediately after passkey creation to show the user their
17
- * address — no transaction or network call needed.
18
- */
19
- function deriveWalletAddress(params) {
20
- const { passkeyId, deployerPublicKey, network = 'testnet' } = params;
21
- const networkPassphrase = PASSPHRASES[network];
22
- const salt = (0, stellar_sdk_1.hash)(Buffer.from(passkeyId));
23
- const networkId = (0, stellar_sdk_1.hash)(Buffer.from(networkPassphrase));
24
- const preimage = stellar_sdk_1.xdr.HashIdPreimage.envelopeTypeContractId(new stellar_sdk_1.xdr.HashIdPreimageContractId({
25
- networkId: Buffer.from(networkId),
26
- contractIdPreimage: stellar_sdk_1.xdr.ContractIdPreimage.contractIdPreimageFromAddress(new stellar_sdk_1.xdr.ContractIdPreimageFromAddress({
27
- address: new stellar_sdk_1.Address(deployerPublicKey).toScAddress(),
28
- salt: Buffer.from(salt),
29
- })),
30
- }));
31
- const contractId = (0, stellar_sdk_1.hash)(preimage.toXDR());
32
- return stellar_sdk_1.StrKey.encodeContract(contractId);
33
- }