@geeqdev/geeq-sdk 0.0.0-stage → 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.
Files changed (2) hide show
  1. package/README.md +593 -2
  2. package/package.json +70 -4
package/README.md CHANGED
@@ -1,3 +1,594 @@
1
- # Temporary Holding Version
1
+ # @geeqdev/geeq-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Add Geeq to a web app without learning the Geeq protocol.
4
+
5
+ The SDK creates wallets, reads the chain, picks a node, builds and signs transactions,
6
+ and tells you whether the chain accepted them. It runs in the browser and in Node.
7
+
8
+ > **Version 0.0.1 (early).** Supports wallets, attestations, coin transfers and NFTs.
9
+ > The SDK **signs** transactions, but **your app sends them** (one `fetch` call, shown below).
10
+
11
+ ---
12
+
13
+ ## Contents
14
+
15
+ - [Install](#install)
16
+ - [Quick start](#quick-start)
17
+ - [Key ideas](#key-ideas)
18
+ - [Wallets](#wallets)
19
+ - [Connecting to a chain](#connecting-to-a-chain)
20
+ - [Transactions](#transactions)
21
+ - [Sending and tracking](#sending-and-tracking)
22
+ - [Amounts and fees](#amounts-and-fees)
23
+ - [Rules to know](#rules-to-know)
24
+ - [Errors](#errors)
25
+ - [Setup notes](#setup-notes)
26
+ - [Reference](#reference)
27
+
28
+ ---
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ npm install @geeqdev/geeq-sdk
34
+ ```
35
+
36
+ Needs Node 20+ or any browser with WebAssembly. Nothing else to install.
37
+
38
+ ---
39
+
40
+ ## Quick start
41
+
42
+ ```ts
43
+ import {
44
+ initChain, keypairFromMnemonic, attest, GeeqSocket, submitRoute, attestationSubmitBody,
45
+ } from "@geeqdev/geeq-sdk";
46
+
47
+ // 1. Connect to a chain and open a socket to its node. Do this once, at startup.
48
+ const chain = await initChain({
49
+ discovery: simulatorUrl, // the simulator for your environment
50
+ chainName: "test", // which of its chains to use
51
+ penniesPerGeeq, // your app's setting, see "Amounts and fees"
52
+ });
53
+ const socket = await GeeqSocket.connect({ endpoint: chain.endpoint });
54
+
55
+ // 2. Load the user's wallet from their recovery phrase.
56
+ const keypair = await keypairFromMnemonic(phrase);
57
+
58
+ // 3. Build and sign a transaction. Here: attest a file.
59
+ const tx = await attest({
60
+ keypair,
61
+ content: fileInput.files[0], // a File, string or bytes
62
+ metadata: ["invoice-2026-09"], // optional public labels
63
+ });
64
+
65
+ // 4. Start listening, THEN send it.
66
+ const tracked = await socket.track(tx);
67
+ const response = await fetch(submitRoute(tx.nodeUrl), {
68
+ method: "POST",
69
+ headers: { "content-type": "application/json" },
70
+ body: attestationSubmitBody(tx.transaction, tx.payload),
71
+ });
72
+ if (!response.ok) tracked.refused({ status: response.status });
73
+
74
+ // 5. Wait for the result (about one block).
75
+ const outcome = await tracked.settled(); // status: "confirmed" | "rejected" | "unknown"
76
+ ```
77
+
78
+ Every other transaction follows the same five steps.
79
+
80
+ ---
81
+
82
+ ## Key ideas
83
+
84
+ | Term | Meaning |
85
+ |---|---|
86
+ | **Recovery phrase** | 12–24 English words. This *is* the wallet: whoever has it owns the account. |
87
+ | **Keypair** | Made from the phrase. It signs transactions. |
88
+ | **Account id** | A 32-byte hash of the public key. The chain uses it to identify the account. |
89
+ | **Address** | A string form of the account, for one specific chain. |
90
+ | **Node** | The server you talk to. The SDK picks one for you. |
91
+ | **Nonce** | The account's transaction counter. It stops a transaction from being replayed. |
92
+ | **Pennies / Geeq** | The chain counts pennies. "Geeq" is a display unit your app defines. |
93
+
94
+ You never supply the chain number, node number or nonce yourself. The SDK reads them from
95
+ the chain.
96
+
97
+ ---
98
+
99
+ ## Wallets
100
+
101
+ ### Recovery phrases
102
+
103
+ ```ts
104
+ import { generateMnemonic, validateMnemonic, normalizeMnemonic } from "@geeqdev/geeq-sdk";
105
+
106
+ const phrase = generateMnemonic(); // 12 words
107
+ generateMnemonic({ words: 24 }); // 12, 15, 18, 21 or 24
108
+ validateMnemonic(phrase); // true
109
+ normalizeMnemonic(pastedText); // tidies spacing and case
110
+ ```
111
+
112
+ These are instant and work before the WebAssembly engine has loaded.
113
+
114
+ ### Keypairs
115
+
116
+ ```ts
117
+ import {
118
+ keypairFromMnemonic, generateKeypair, importKeypair, keypairFromSecretKey,
119
+ } from "@geeqdev/geeq-sdk";
120
+
121
+ const keypair = await keypairFromMnemonic(phrase);
122
+ // Also: generateKeypair(), importKeypair(publicKey, privateKey), keypairFromSecretKey(privateKey)
123
+
124
+ keypair.publicKey // 32 bytes
125
+ keypair.privateKey // 32 bytes, keep secret
126
+ keypair.accountId // 32 bytes
127
+ keypair.getAddress({ chainNumber: 1 }) // wallet address
128
+ keypair.sign(bytes)
129
+ keypair.dispose() // call this when you are done with the keypair
130
+ ```
131
+
132
+ `keypairFromMnemonic` takes options. Each one changes the key you get:
133
+
134
+ ```ts
135
+ await keypairFromMnemonic(phrase, {
136
+ passphrase: "", // optional extra password
137
+ coinType: "mainnet", // or "testnet"
138
+ recordType: "coin-asset",
139
+ account: 0,
140
+ addressIndex: 0,
141
+ });
142
+ ```
143
+
144
+ - **Call `dispose()`** when you replace or drop a keypair. It frees memory in the engine.
145
+ After that, `sign()` throws.
146
+ - **In Vue or React state**, don't make it deeply reactive. Use `shallowRef` + `markRaw` in
147
+ Vue, or keep it out of `useState` in React.
148
+ - `JSON.stringify(keypair)` only outputs the public key, so the private key can't leak into
149
+ logs.
150
+
151
+ ### Addresses
152
+
153
+ ```ts
154
+ import { createWalletAddress, decodeWalletAddress, isValidWalletAddress } from "@geeqdev/geeq-sdk";
155
+
156
+ const address = keypair.getAddress({ chainNumber: 1 });
157
+ await createWalletAddress({ publicKey, chainNumber: 1 }); // same, without a keypair
158
+ await decodeWalletAddress(address); // { accountId, chainNumber, recordType }
159
+ isValidWalletAddress(address); // true
160
+ ```
161
+
162
+ **An address only works on one chain.** The chain number is part of the address, so the
163
+ same key gives different addresses on different chains.
164
+
165
+ ---
166
+
167
+ ## Connecting to a chain
168
+
169
+ ```ts
170
+ import { initChain, GeeqChain } from "@geeqdev/geeq-sdk";
171
+
172
+ // Set a default chain for the whole app (used by attest() and transfer()):
173
+ const chain = await initChain({ discovery: simulatorUrl, chainName: "test", penniesPerGeeq });
174
+
175
+ // Or create a separate instance (for tests, multiple chains, server rendering):
176
+ const other = await GeeqChain.connect({ discovery: simulatorUrl, chainName: "test", penniesPerGeeq });
177
+
178
+ // Or skip discovery if you already know a node:
179
+ await GeeqChain.connect({ endpoint: "https://1.2.3.4:2082", penniesPerGeeq });
180
+ ```
181
+
182
+ With `discovery`, the SDK asks the simulator which nodes are running, picks one at random
183
+ and uses it for everything. You can name the chain by `chainName` or `chainNumber`.
184
+ This works for every chain and environment: pass the simulator URL of the one you use.
185
+
186
+ - Nodes use **port 2082**. If you leave the port out, the SDK adds it.
187
+ - `connect` doesn't read the chain yet. That happens when you build your first transaction,
188
+ so `chain.node` is `undefined` until then.
189
+
190
+ To see the account's current state (balance, nonce, limits):
191
+
192
+ ```ts
193
+ const state = await chain.getChainState({ account: keypair });
194
+ chain.formatGeeq(state.balance); // e.g. "12.5"
195
+ state.minimumTransfer; // smallest allowed transfer, in pennies
196
+ state.metadataLimits; // how many labels, and how long each can be
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Transactions
202
+
203
+ Each method below reads the chain, checks your input, signs, and returns a `tx`. **It
204
+ doesn't send anything.** See [Sending and tracking](#sending-and-tracking).
205
+
206
+ | What | Method | Send with |
207
+ |---|---|---|
208
+ | Attest a file or text | `chain.createAttestation` (or `attest`) | `attestationSubmitBody` |
209
+ | Send coin or NFTs | `chain.createTransfer` (or `transfer`) | `coinTransferSubmitBody` |
210
+ | Create an NFT mint proxy | `chain.createNftMintProxy` | `coinTransferSubmitBody` |
211
+ | Mint an NFT | `chain.mintNft` | `attestationSubmitBody` |
212
+
213
+ Every `tx` includes:
214
+
215
+ ```ts
216
+ tx.transaction // base64, the signed transaction
217
+ tx.payload // base64, attestations and mints only
218
+ tx.nodeUrl // the node you must send it to
219
+ tx.fee // what the chain will charge (see "Amounts and fees")
220
+ tx.transactionHash // 32 bytes, the transaction's id
221
+ tx.chainState // the chain values it was signed with
222
+ ```
223
+
224
+ ### Attest a file
225
+
226
+ Puts the file's hash on the chain. **The file itself never leaves the device.**
227
+
228
+ ```ts
229
+ const tx = await chain.createAttestation({
230
+ keypair,
231
+ content: file, // File, Blob, string, Uint8Array or ArrayBuffer
232
+ metadata: ["invoice-2026-09"], // optional
233
+ });
234
+ ```
235
+
236
+ If you already have a 32-byte hash, pass `attestHash` instead of `content`.
237
+
238
+ **Metadata labels are public.** Use them for ids, not secrets. Each chain limits how many
239
+ labels you can add and how long each one is (in UTF-8 bytes, so one emoji counts as 4). To
240
+ check labels while the user types:
241
+
242
+ ```ts
243
+ const report = inspectMetadata(labels, state.metadataLimits);
244
+ report.withinLimits; // false if any label is too long, or there are too many
245
+ report.entries[0]; // { index: 0, bytes: 40, overLimit: true }
246
+ ```
247
+
248
+ ### Send coin
249
+
250
+ ```ts
251
+ const tx = await chain.createTransfer({
252
+ from: keypair,
253
+ to: recipientAddress,
254
+ amount: { geeq: "1.50" },
255
+ });
256
+ ```
257
+
258
+ Before signing, the SDK checks that:
259
+
260
+ - the address belongs to this chain (`chain-mismatch`),
261
+ - the amount is at least the chain's minimum (`amount-below-minimum`),
262
+ - the sender can pay the amount plus the fee (`insufficient-funds`).
263
+
264
+ ### NFTs
265
+
266
+ NFTs are minted through a **proxy**: a separate account that holds coin to pay for mints.
267
+ The wallet that creates it becomes its **admin**, and only the admin can mint with it.
268
+
269
+ **1. Create a proxy** (once):
270
+
271
+ ```ts
272
+ const tx = await chain.createNftMintProxy({
273
+ admin: keypair,
274
+ amount: { geeq: "5" }, // coin moved onto the proxy to pay for mints
275
+ });
276
+ tx.index; // 1 for the admin's first proxy, then 2, 3…
277
+ tx.proxyAddress; // send coin here to top it up later
278
+ ```
279
+
280
+ **2. Find the admin's proxies** (no storage needed, they are derived from the key):
281
+
282
+ ```ts
283
+ const { proxies, nextIndex } = await chain.findNftMintProxies({ admin: keypair, maxAgeMs: 0 });
284
+ // proxies: [{ index: 1, address, balance, … }]
285
+ ```
286
+
287
+ **3. Mint an NFT:**
288
+
289
+ ```ts
290
+ const tx = await chain.mintNft({
291
+ admin: keypair,
292
+ index: 1, // which proxy to mint with
293
+ content: file, // hashed locally, never sent
294
+ metadata: ["deed", "lot 7"], // optional public labels
295
+ // to: someAddress, // optional, defaults to the admin
296
+ });
297
+ tx.nftId; // the new NFT's id
298
+ ```
299
+
300
+ The proxy pays the fee, not the admin. The same content and labels can only be minted once
301
+ per proxy.
302
+
303
+ **4. List and send NFTs:**
304
+
305
+ ```ts
306
+ const nfts = await chain.findNfts({ owner: keypair, maxAgeMs: 0 });
307
+ // [{ id, attestHash, metadata, counterparty }]
308
+
309
+ const tx = await chain.createTransfer({
310
+ from: keypair,
311
+ to: recipientAddress,
312
+ nfts: [nfts[0].id], // one or more
313
+ // amount is optional, defaults to the chain's minimum transfer
314
+ });
315
+ ```
316
+
317
+ New proxies and NFTs are listed only after a block confirms them. Pass `maxAgeMs: 0` to
318
+ skip the cache when you check right after sending.
319
+
320
+ ---
321
+
322
+ ## Sending and tracking
323
+
324
+ ```ts
325
+ import { GeeqSocket, submitRoute, attestationSubmitBody } from "@geeqdev/geeq-sdk";
326
+
327
+ const socket = await GeeqSocket.connect({ endpoint: chain.endpoint }); // once per app
328
+
329
+ const tracked = await socket.track(tx); // 1. listen first
330
+ const response = await fetch(submitRoute(tx.nodeUrl), { // 2. then send
331
+ method: "POST",
332
+ headers: { "content-type": "application/json" },
333
+ body: attestationSubmitBody(tx.transaction, tx.payload),
334
+ // for transfers and proxies: coinTransferSubmitBody(tx.transaction)
335
+ });
336
+ if (!response.ok) {
337
+ tracked.refused({ status: response.status, message: await response.text() });
338
+ }
339
+
340
+ const outcome = await tracked.settled(); // 3. wait for the verdict
341
+ ```
342
+
343
+ **Always call `track` before you send.** Otherwise you can miss the result. If `track`
344
+ throws, don't send.
345
+
346
+ **A `200` response is not a confirmation.** It only means the node received a valid
347
+ transaction. Wait for `settled()` before you show success.
348
+
349
+ `settled()` never throws. It resolves to one of:
350
+
351
+ | `status` | `reason` | Meaning |
352
+ |---|---|---|
353
+ | `confirmed` | | Done. It's in a block. |
354
+ | `rejected` | `invalid` | Failed when the block was built (usually nonce or balance). **The fee is still charged.** |
355
+ | `rejected` | `nonsane` | The node refused it. |
356
+ | `rejected` | `refused` | Your POST failed (you reported it with `tracked.refused`). |
357
+ | `rejected` | `incorrectly-included` | A dishonest node included it. Not your fault. |
358
+ | `unknown` | `timeout` | No answer in 5 minutes. **It may still have succeeded**: check the account's nonce before retrying. |
359
+ | `unknown` | `closed`, `released` | The socket was closed, or you called `tracked.release()`. |
360
+
361
+ Show `outcome.message` to users, but write your logic against `reason`. For a progress
362
+ indicator, use `tracked.onChange(t => …)`.
363
+
364
+ The socket reconnects on its own. Keep **one socket per chain** for the life of the page
365
+ and call `socket.dispose()` when you're done.
366
+
367
+ ### Other socket events
368
+
369
+ ```ts
370
+ await socket.subscribe([keypair]); // get events for these accounts
371
+ socket.onTxStatus((event) => { /* any tx status, including incoming coin */ });
372
+ socket.on("coin-account-update", (payload) => { /* … */ });
373
+ socket.onConnectionChange((state) => setOffline(state !== "open"));
374
+ ```
375
+
376
+ Incoming transfers arrive through `onTxStatus` with `event.direction === "incoming"`.
377
+ Use `event.id` to remove duplicates.
378
+
379
+ ### Looking up a transaction later
380
+
381
+ After a page reload the socket can't tell you what happened. Ask a block explorer instead:
382
+
383
+ ```ts
384
+ import { getExplorerTransaction, transactionIdOf } from "@geeqdev/geeq-sdk";
385
+
386
+ const found = await getExplorerTransaction({
387
+ explorerUrl: "https://blkexp.geeq-test.io",
388
+ refHash: transactionIdOf(tx), // save this before the reload
389
+ });
390
+ // null = the explorer doesn't have it yet (or it was never included)
391
+ // otherwise: { status, type, block, from, to, values, … }
392
+ ```
393
+
394
+ ---
395
+
396
+ ## Amounts and fees
397
+
398
+ The chain only counts **pennies**. How many pennies make one **Geeq** is up to your app.
399
+ You set it once, as `penniesPerGeeq`, when you connect. It must be a bigint power of ten
400
+ (`1n`, `10n`, `100n`, …). **Use the same value as your wallet and explorer**
401
+ (`VUE_APP_BALANCE_DIVISOR` in Geeq's apps), or the numbers won't match.
402
+
403
+ Every amount must say its unit. A bare number like `1.5` is rejected, so a mix-up can't
404
+ send the wrong amount.
405
+
406
+ ```ts
407
+ { geeq: "12.34" } // what a person types
408
+ { pennies: 1234n } // what the chain counts
409
+
410
+ chain.toPennies({ geeq: "1.5" }); // → bigint
411
+ chain.formatGeeq(pennies); // → "1.5"
412
+ ```
413
+
414
+ If an amount has more decimal places than your unit allows, it is rejected, not rounded.
415
+
416
+ **Fees.** `tx.fee` is the exact fee, in pennies, paid on top of the amount:
417
+
418
+ ```ts
419
+ chain.formatGeeq(tx.fee.total); // data + coin
420
+ tx.fee.data; // charged per byte of the transaction
421
+ tx.fee.coin; // 0.25% of the amount sent (capped)
422
+ ```
423
+
424
+ The sender pays the fee. For a mint, the proxy pays it.
425
+
426
+ ---
427
+
428
+ ## Rules to know
429
+
430
+ These cause most problems:
431
+
432
+ 1. **Only one transaction per account per block.** Two transactions with the same nonce
433
+ are *both* rejected. The SDK blocks a second build with `nonce-collision`. If the first
434
+ one was never sent, give its nonce back and build again:
435
+ ```ts
436
+ chain.discard(tx); // only for a tx you did NOT send
437
+ ```
438
+ For mints, the rule applies to the proxy instead of the admin.
439
+
440
+ 2. **Send within about 9 blocks.** After that the node rejects the transaction. If the user
441
+ waited on a confirm screen, check first:
442
+ ```ts
443
+ if (await chain.isExpired(tx)) { chain.discard(tx); /* build again */ }
444
+ ```
445
+
446
+ 3. **Always send to `tx.nodeUrl`.** The transaction names its node. Any other node rejects it.
447
+
448
+ 4. **New accounts don't exist yet.** A new wallet isn't on the chain until it receives coin,
449
+ so you'll get `account-not-found`. Show a "fund your account" message, not an error.
450
+
451
+ 5. **Browser network errors all look the same.** CORS, an untrusted certificate, mixed
452
+ content (an https page calling an http node) and a dead node all show up as
453
+ `network-unreachable`. The error message lists the possible causes. Nodes use bare IP
454
+ addresses, so the browser must trust their certificates.
455
+
456
+ 6. **"Processing" doesn't mean the node is running.** A crashed node can still be listed as
457
+ processing. The only way to know is to call it.
458
+
459
+ ---
460
+
461
+ ## Errors
462
+
463
+ Every error is a `GeeqError` with a stable `code`. **Check `code`, not the message.**
464
+
465
+ ```ts
466
+ try {
467
+ await attest({ keypair, content: file });
468
+ } catch (error) {
469
+ if (error instanceof GeeqError && error.code === "account-not-found") {
470
+ // ask the user to fund the account
471
+ }
472
+ }
473
+ ```
474
+
475
+ | Code | Meaning | What to do |
476
+ |---|---|---|
477
+ | `not-initialized` | Engine or default chain not ready | Await `initGeeq()` / `initChain()` first |
478
+ | `init-failed` | Engine failed to load | Check your bundler setup (see [Setup notes](#setup-notes)) |
479
+ | `invalid-key` | Wrong key length, or keys don't match | |
480
+ | `invalid-address` | Bad wallet address | |
481
+ | `invalid-argument` | A value is out of range | |
482
+ | `network-unreachable` | No response from the server | See rule 5 above |
483
+ | `node-error` | Server returned an error | Check `error.status` and `error.nodeDetails` |
484
+ | `ledger-unreadable` | Server replied, but not with chain data | Check the URL |
485
+ | `chain-mismatch` | Wrong chain (node or address) | Use the right chain |
486
+ | `account-not-found` | Account has never received coin | Fund it |
487
+ | `account-not-attestable` | Account type can't sign this | Use a `coin-asset` key |
488
+ | `node-unusable` | Node can't be identified or won't take transactions | Use another node; the message lists some |
489
+ | `metadata-too-large` | Labels too long or too many | Shorten them; the message says which one |
490
+ | `nonce-collision` | Second transaction in the same block | See rule 1 above |
491
+ | `insufficient-funds` | Can't pay amount + fee | Send less, or add funds |
492
+ | `amount-below-minimum` | Amount below the chain's minimum | Send more |
493
+ | `nft-not-owned` | Sender doesn't hold that NFT | Refresh with `findNfts` |
494
+ | `socket-unreachable` | Socket couldn't open | On Node 20, pass `webSocket` (below) |
495
+ | `socket-closed` | Socket was disposed | Connect a new one |
496
+
497
+ ---
498
+
499
+ ## Setup notes
500
+
501
+ ### WebAssembly engine
502
+
503
+ The engine (~2 MB) loads on first use. To control when it loads (for example, to show a
504
+ spinner), call it at startup:
505
+
506
+ ```ts
507
+ await initGeeq();
508
+ isGeeqReady(); // true
509
+ ```
510
+
511
+ Functions ending in `Sync` need the engine loaded first. Phrases, hashing, hex and base64
512
+ don't need it at all.
513
+
514
+ **Vite** needs one line of config:
515
+
516
+ ```ts
517
+ export default defineConfig({ optimizeDeps: { exclude: ["@geeqdev/geeq-sdk"] } });
518
+ ```
519
+
520
+ Webpack 5, Rollup and Parcel work without config. To load the engine from your own URL:
521
+ `initGeeq({ wasm: "/assets/geeq_core_bg.wasm" })`.
522
+
523
+ ### Node 20
524
+
525
+ Node 20 has no built-in WebSocket. Pass one in (Node 22+ and browsers don't need this):
526
+
527
+ ```ts
528
+ import WebSocket from "ws";
529
+ GeeqSocket.connect({ endpoint, webSocket: (url) => new WebSocket(url) });
530
+ ```
531
+
532
+ ### Testing without a node
533
+
534
+ Pass a fake `transport` (anything shaped like `fetch`):
535
+
536
+ ```ts
537
+ const chain = await GeeqChain.connect({
538
+ endpoint: "https://10.0.0.1:2082",
539
+ penniesPerGeeq,
540
+ transport: async () => ({ ok: true, status: 200, text: async () => JSON.stringify({ result: ledger }) }),
541
+ });
542
+ ```
543
+
544
+ You can also pass a fake `webSocket` to `GeeqSocket.connect`. Call `GeeqChain.reset()` in
545
+ `afterEach` so the default chain doesn't leak between tests.
546
+
547
+ `transport` is also how you route requests through your own proxy if CORS blocks you.
548
+
549
+ ### Signing offline
550
+
551
+ To sign without a network (for tests, replays or air-gapped machines), give the chain
552
+ values yourself:
553
+
554
+ ```ts
555
+ const tx = await createAttestationTx({
556
+ keypair, attestHash, chainNumber: 1, blockNumber: 42, nodeNumber: 3, nonce: 7,
557
+ });
558
+ // or: createAttestationTx({ keypair, attestHash, chainState })
559
+ ```
560
+
561
+ The output is byte-for-byte the same as the online version. Other offline builders:
562
+ `createCoinTransferTx`, `createNftMintProxyTx`, `createNftMintTx`. Offline builders skip all
563
+ balance and ownership checks.
564
+
565
+ ---
566
+
567
+ ## Reference
568
+
569
+ All exports. Names marked `(Sync)` also have a synchronous version that needs a loaded engine.
570
+
571
+ | Area | Exports |
572
+ |---|---|
573
+ | Engine | `initGeeq`, `isGeeqReady` |
574
+ | Phrases | `generateMnemonic`, `validateMnemonic`, `normalizeMnemonic`, `MNEMONIC_WORD_COUNTS` |
575
+ | Keypairs | `generateKeypair(Sync)`, `importKeypair(Sync)`, `keypairFromSecretKey(Sync)`, `keypairFromMnemonic(Sync)`, `verifySignature`, `accountIdFromPublicKey`, `ACCOUNT_ID_BYTES`, `PUBLIC_KEY_BYTES` |
576
+ | Addresses | `createWalletAddress(Sync)`, `decodeWalletAddress(Sync)`, `isValidWalletAddress`, `GEEQ_RECORD_TYPES`, `DEFAULT_RECORD_TYPE` |
577
+ | Chains and nodes | `GeeqChain`, `GeeqNode`, `initChain`, `attest`, `transfer`, `selectNode`, `GEEQ_NODE_PORT`, `discover`, `listChains`, `listNodes`, `defaultTransport` |
578
+ | Transactions (offline) | `createAttestationTx(Sync)`, `createCoinTransferTx(Sync)`, `createNftMintProxyTx(Sync)`, `createNftMintTx(Sync)` |
579
+ | Amounts | `toPennies`, `formatGeeq` |
580
+ | Content and encoding | `attestHashOf`, `attestHashOfSync`, `readContent`, `assertAttestHash`, `ATTEST_HASH_BYTES`, `inspectMetadata`, `toHex`, `fromHex`, `toBase64`, `fromBase64` |
581
+ | Sockets | `GeeqSocket`, `GeeqTrackedTx`, `transactionIdOf`, `defaultWebSocketFactory` |
582
+ | Block explorer | `getExplorerTransaction` |
583
+ | Node routes | `ledgerRoute`, `submitRoute`, `socketRoute`, `attestationSubmitBody`, `coinTransferSubmitBody` |
584
+ | Ledgers | `decodeValidationLedger`, `GeeqLedger` |
585
+ | Errors | `GeeqError`, `GeeqErrorCode` |
586
+
587
+ TypeScript types are exported next to their functions (`GeeqKeypair`, `AttestationTx`,
588
+ `GeeqChainState`, `GeeqFee`, `GeeqTxOutcome`, and others).
589
+
590
+ ### Not supported yet
591
+
592
+ - **Sending transactions.** Your app does the `fetch`.
593
+ - **Getting a result after a page reload.** Save `transactionIdOf(tx)` and use the explorer.
594
+ - **Tokens and other asset types.** Only coin, attestations and NFTs for now.
package/package.json CHANGED
@@ -1,6 +1,72 @@
1
1
  {
2
2
  "name": "@geeqdev/geeq-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Geeq blockchain SDK for wallets, transfers and NFTs. Works in the browser and Node.js.",
5
+ "keywords": [
6
+ "geeq",
7
+ "blockchain",
8
+ "sdk",
9
+ "wallet",
10
+ "transactions",
11
+ "nft",
12
+ "typescript",
13
+ "browser"
14
+ ],
15
+ "license": "UNLICENSED",
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "main": "./dist/node/index.cjs",
22
+ "module": "./dist/browser/index.js",
23
+ "browser": "./dist/browser/index.js",
24
+ "types": "./dist/types/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "browser": {
28
+ "types": "./dist/types/index.d.ts",
29
+ "default": "./dist/browser/index.js"
30
+ },
31
+ "import": {
32
+ "types": "./dist/types/index.d.ts",
33
+ "default": "./dist/node/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/types/index.d.cts",
37
+ "default": "./dist/node/index.cjs"
38
+ }
39
+ },
40
+ "./geeq_core_bg.wasm": "./dist/browser/geeq_core_bg.wasm",
41
+ "./package.json": "./package.json"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md"
46
+ ],
47
+ "publishConfig": {
48
+ "registry": "https://registry.npmjs.org/",
49
+ "access": "public"
50
+ },
51
+ "scripts": {
52
+ "build-example": "tsup && node scripts/copy-wasm.mjs",
53
+ "typecheck": "tsc -p tsconfig.json --noEmit",
54
+ "test": "jest",
55
+ "smoke": "node scripts/smoke.mjs",
56
+ "build": "npm run build"
57
+ },
58
+ "dependencies": {
59
+ "@noble/hashes": "^1.8.0",
60
+ "@scure/bip39": "^1.6.0",
61
+ "capnp-ts": "^0.4.0"
62
+ },
63
+ "devDependencies": {
64
+ "@jest/globals": "^30.5.2",
65
+ "@types/jest": "^30.0.0",
66
+ "@types/node": "^22.10.0",
67
+ "jest": "^30.5.2",
68
+ "ts-jest": "^29.2.5",
69
+ "tsup": "^8.3.5",
70
+ "typescript": "^5.6.3"
71
+ }
72
+ }