@geeqdev/geeq-sdk 0.1.0 → 0.1.1

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 (122) hide show
  1. package/.idea/geeq_sdk.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/.nvmrc +1 -0
  5. package/CONTRIBUTING.md +20 -0
  6. package/example/.env.example +4 -0
  7. package/example/README.md +304 -0
  8. package/example/USAGE.md +74 -0
  9. package/example/index.html +12 -0
  10. package/example/package-lock.json +1557 -0
  11. package/example/package.json +25 -0
  12. package/example/scripts/browser-check.mjs +1421 -0
  13. package/example/scripts/match-derivation.mjs +65 -0
  14. package/example/src/App.vue +138 -0
  15. package/example/src/components/AuthorityPanel.vue +277 -0
  16. package/example/src/components/ChainPanel.vue +267 -0
  17. package/example/src/components/ChainState.vue +59 -0
  18. package/example/src/components/CopyButton.vue +25 -0
  19. package/example/src/components/CreateAssetPanel.vue +313 -0
  20. package/example/src/components/DigitalAssetsPanel.vue +68 -0
  21. package/example/src/components/EvidencePanel.vue +396 -0
  22. package/example/src/components/ExplorerPanel.vue +119 -0
  23. package/example/src/components/KeypairPanel.vue +182 -0
  24. package/example/src/components/PhrasePanel.vue +105 -0
  25. package/example/src/components/SocketWarning.vue +18 -0
  26. package/example/src/components/StepGate.vue +24 -0
  27. package/example/src/components/TransferPanel.vue +416 -0
  28. package/example/src/components/TxDeadline.vue +19 -0
  29. package/example/src/components/TxFee.vue +31 -0
  30. package/example/src/components/TxStatus.vue +105 -0
  31. package/example/src/composables/useSocketConnection.ts +80 -0
  32. package/example/src/env.d.ts +12 -0
  33. package/example/src/main.ts +5 -0
  34. package/example/src/pennies.ts +7 -0
  35. package/example/src/style.css +149 -0
  36. package/example/tsconfig.json +19 -0
  37. package/example/vite.config.ts +26 -0
  38. package/jest.config.js +18 -0
  39. package/package.json +1 -5
  40. package/scripts/copy-wasm.mjs +21 -0
  41. package/scripts/smoke.mjs +382 -0
  42. package/src/address/record-type.ts +77 -0
  43. package/src/address/wallet-address.ts +105 -0
  44. package/src/amount.ts +143 -0
  45. package/src/assert.ts +30 -0
  46. package/src/capnp/README.md +15 -0
  47. package/src/capnp/adminNetTx.capnp.ts +66 -0
  48. package/src/capnp/appLayBlock.capnp.ts +26 -0
  49. package/src/capnp/appLayLedger.capnp.ts +35 -0
  50. package/src/capnp/appLayRec.capnp.ts +159 -0
  51. package/src/capnp/chainStateView.capnp.ts +49 -0
  52. package/src/capnp/coinAcctRec.capnp.ts +193 -0
  53. package/src/capnp/genesisBlock.capnp.ts +56 -0
  54. package/src/capnp/hubContentStmt.capnp.ts +59 -0
  55. package/src/capnp/misc.capnp.ts +19 -0
  56. package/src/capnp/netActorRec.capnp.ts +64 -0
  57. package/src/capnp/nodeContentStmt.capnp.ts +62 -0
  58. package/src/capnp/nodePayloadList.capnp.ts +28 -0
  59. package/src/capnp/nodeTxList.capnp.ts +37 -0
  60. package/src/capnp/paramAcctRec.capnp.ts +669 -0
  61. package/src/capnp/payload.capnp.ts +261 -0
  62. package/src/capnp/proxyAcctRec.capnp.ts +259 -0
  63. package/src/capnp/relayContentStmt.capnp.ts +85 -0
  64. package/src/capnp/sysAcctRec.capnp.ts +47 -0
  65. package/src/capnp/userAppTx.capnp.ts +269 -0
  66. package/src/capnp/userValTx.capnp.ts +1999 -0
  67. package/src/capnp/valLayBlock.capnp.ts +93 -0
  68. package/src/capnp/valLayLedger.capnp.ts +80 -0
  69. package/src/chain/geeq-chain.ts +1094 -0
  70. package/src/chain/geeq-node.ts +105 -0
  71. package/src/chain/nonce-guard.ts +73 -0
  72. package/src/chain/select.ts +149 -0
  73. package/src/crypto/account-id.ts +83 -0
  74. package/src/crypto/keypair.ts +221 -0
  75. package/src/crypto/mnemonic.ts +69 -0
  76. package/src/encoding/base64.ts +58 -0
  77. package/src/encoding/hex.ts +42 -0
  78. package/src/errors.ts +122 -0
  79. package/src/index.ts +195 -0
  80. package/src/ledger/coin-accounts.ts +73 -0
  81. package/src/ledger/decode.ts +147 -0
  82. package/src/ledger/geeq-ledger.ts +184 -0
  83. package/src/ledger/net-actors.ts +129 -0
  84. package/src/ledger/nft-records.ts +49 -0
  85. package/src/ledger/params.ts +82 -0
  86. package/src/ledger/proxy-accounts.ts +51 -0
  87. package/src/net/discovery.ts +231 -0
  88. package/src/net/explorer.ts +123 -0
  89. package/src/net/read-ledger.ts +58 -0
  90. package/src/net/routes.ts +79 -0
  91. package/src/net/transport.ts +221 -0
  92. package/src/net/websocket.ts +94 -0
  93. package/src/runtime/bindings.ts +38 -0
  94. package/src/runtime/init.ts +49 -0
  95. package/src/runtime/wasm-loader.browser.ts +22 -0
  96. package/src/runtime/wasm-loader.node.ts +16 -0
  97. package/src/socket/geeq-socket.ts +827 -0
  98. package/src/socket/tracked-tx.ts +319 -0
  99. package/src/socket/tx-status.ts +131 -0
  100. package/src/tx/attestation.ts +222 -0
  101. package/src/tx/content.ts +82 -0
  102. package/src/tx/encode.ts +107 -0
  103. package/src/tx/expiry.ts +11 -0
  104. package/src/tx/fee.ts +31 -0
  105. package/src/tx/metadata.ts +100 -0
  106. package/src/tx/nft.ts +108 -0
  107. package/src/tx/proxy.ts +156 -0
  108. package/src/tx/transfer.ts +162 -0
  109. package/src/types.ts +503 -0
  110. package/src/wasm/README.md +22 -0
  111. package/src/wasm/browser/geeq_core.d.ts +1224 -0
  112. package/src/wasm/browser/geeq_core.js +6014 -0
  113. package/src/wasm/browser/geeq_core_bg.wasm +0 -0
  114. package/src/wasm/browser/geeq_core_bg.wasm.d.ts +447 -0
  115. package/src/wasm/nodejs/geeq_core.d.ts +753 -0
  116. package/src/wasm/nodejs/geeq_core.js +6016 -0
  117. package/src/wasm/nodejs/geeq_core_bg.wasm +0 -0
  118. package/src/wasm/nodejs/geeq_core_bg.wasm.d.ts +447 -0
  119. package/src/wasm/nodejs/package.json +3 -0
  120. package/tsconfig.build.json +8 -0
  121. package/tsconfig.json +20 -0
  122. package/tsup.config.ts +60 -0
@@ -0,0 +1,182 @@
1
+ <script setup lang="ts">
2
+ import { markRaw, ref, shallowRef, watch } from 'vue'
3
+ import { GEEQ_RECORD_TYPES, GeeqError, keypairFromMnemonic, toBase64 } from '@geeqdev/geeq-sdk'
4
+ import type { GeeqCoinType, GeeqKeypair, GeeqRecordType } from '@geeqdev/geeq-sdk'
5
+ import CopyButton from './CopyButton.vue'
6
+
7
+ const props = defineProps<{ mnemonic: string; engineReady: boolean }>()
8
+
9
+ // Evidence must name the chain it targets, and it has to be the chain this
10
+ // account lives on — so the number is owned by App and shared, not duplicated here.
11
+ const chainNumber = defineModel<number>('chainNumber', { required: true })
12
+ // The signing key is shared the same way. Emitted as null whenever it is disposed,
13
+ // so no other panel can reach a freed engine handle.
14
+ const emit = defineEmits<{ (e: 'keypair', keypair: GeeqKeypair | null): void }>()
15
+ // Fixed, deliberately not exposed. It is part of the SLIP-0010 derivation path
16
+ // (BIP-44 coin type: testnet 1, mainnet 1019), so it changes the key itself rather
17
+ // than just labelling the address the way chainNumber does. Note this overrides the
18
+ // SDK's own 'testnet' default — passing it explicitly on every call is what keeps
19
+ // this demo lined up with the Geeq apps.
20
+ // GeeqCoinType is a string union ('mainnet' | 'testnet'), not an enum — the SDK
21
+ // keeps the readable name in its public API and maps it to the engine's numeric
22
+ // CoinType internally, so there is no GeeqCoinType.Mainnet to reference.
23
+ const COIN_TYPE: GeeqCoinType = 'mainnet'
24
+ // BIP-39 passphrase ("25th word"). Folded into the seed, so it changes the key.
25
+ const passphrase = ref('')
26
+ // Record type sits in the derivation path *and* in the address envelope, so it is
27
+ // passed to both calls below — deriving under one record type and encoding under
28
+ // another would yield an address the owner of the key could not reproduce.
29
+ const recordType = ref<GeeqRecordType>('coin-asset')
30
+ // shallowRef + markRaw: a keypair is a class instance holding an engine handle and
31
+ // typed arrays. Deep reactivity would proxy those arrays before they reach
32
+ // WebAssembly, and buys nothing — the object is replaced wholesale, never mutated.
33
+ const keypair = shallowRef<GeeqKeypair | null>(null)
34
+ const address = ref('')
35
+ const error = ref('')
36
+ const busy = ref(false)
37
+ const revealPrivateKey = ref(false)
38
+
39
+
40
+ // A keypair holds an engine-side handle; release it before replacing or dropping it.
41
+ function clear() {
42
+ keypair.value?.dispose()
43
+ keypair.value = null
44
+ address.value = ''
45
+ error.value = ''
46
+ revealPrivateKey.value = false
47
+ emit('keypair', null)
48
+ }
49
+
50
+ // The phrase or the derivation path changing invalidates any keypair derived from
51
+ // the previous one.
52
+ watch([() => props.mnemonic, passphrase, recordType], clear)
53
+
54
+ async function derive() {
55
+ clear()
56
+ busy.value = true
57
+ try {
58
+ const kp = await keypairFromMnemonic(props.mnemonic, {
59
+ coinType: COIN_TYPE,
60
+ passphrase: passphrase.value,
61
+ recordType: recordType.value,
62
+ })
63
+ keypair.value = markRaw(kp)
64
+ address.value = kp.getAddress({
65
+ chainNumber: chainNumber.value,
66
+ recordType: recordType.value,
67
+ })
68
+ emit('keypair', kp)
69
+ } catch (e) {
70
+ error.value =
71
+ e instanceof GeeqError ? `${e.code}: ${e.message}` : (e as Error).message
72
+ } finally {
73
+ busy.value = false
74
+ }
75
+ }
76
+
77
+ // The address encodes the chain, so changing it re-derives the address only —
78
+ // the keypair itself is unaffected.
79
+ watch(chainNumber, (next) => {
80
+ if (keypair.value) {
81
+ address.value = keypair.value.getAddress({
82
+ chainNumber: next,
83
+ recordType: recordType.value,
84
+ })
85
+ }
86
+ })
87
+ </script>
88
+
89
+ <template>
90
+ <section class="card">
91
+ <h2><span class="step">2</span>Keypair &amp; address</h2>
92
+
93
+ <div class="row">
94
+ <span class="ctl">
95
+ <label for="chain">Chain number</label>
96
+ <input id="chain" v-model.number="chainNumber" type="number" min="0" />
97
+ </span>
98
+ <span class="ctl">
99
+ <label for="record">Record type</label>
100
+ <select id="record" v-model="recordType">
101
+ <option v-for="type in GEEQ_RECORD_TYPES" :key="type" :value="type">{{ type }}</option>
102
+ </select>
103
+ </span>
104
+ <span class="ctl">
105
+ <label for="passphrase">Passphrase</label>
106
+ <input
107
+ id="passphrase"
108
+ v-model="passphrase"
109
+ type="text"
110
+ placeholder="empty — type here"
111
+ :class="{ unset: passphrase === '' }"
112
+ />
113
+ </span>
114
+ <button
115
+ class="primary"
116
+ :disabled="!mnemonic || !engineReady || busy"
117
+ @click="derive"
118
+ >
119
+ {{ busy ? 'Deriving…' : 'Derive keypair' }}
120
+ </button>
121
+ </div>
122
+
123
+ <p v-if="!engineReady" class="empty">Waiting for the engine to finish loading…</p>
124
+
125
+ <div v-if="error" class="warn">{{ error }}</div>
126
+
127
+ <template v-if="keypair">
128
+ <div class="field">
129
+ <div class="k">
130
+ Address · base58 · chain {{ chainNumber }} · {{ COIN_TYPE }} · {{ recordType }} ·
131
+ <template v-if="passphrase">passphrase “{{ passphrase }}”</template>
132
+ <template v-else>no passphrase</template>
133
+ </div>
134
+ <div class="v">{{ address }}</div>
135
+ <div class="row" style="margin-top: 7px"><CopyButton :value="address" /></div>
136
+ </div>
137
+
138
+ <div class="field">
139
+ <div class="k">Public key · base64 · 32 bytes</div>
140
+ <div class="v">{{ toBase64(keypair.publicKey) }}</div>
141
+ </div>
142
+
143
+ <div class="field">
144
+ <div class="k">Account id · base64 · 32 bytes · BLAKE3 of the public key</div>
145
+ <div class="v">{{ toBase64(keypair.accountId) }}</div>
146
+ </div>
147
+
148
+ <div class="field">
149
+ <div class="k">Private key · 32 bytes</div>
150
+ <div v-if="revealPrivateKey" class="v">{{ toBase64(keypair.privateKey) }}</div>
151
+ <div v-else class="v" style="color: var(--muted)">••••••••••••••••••••••••••••••••</div>
152
+ <div class="row" style="margin-top: 7px">
153
+ <button class="small" @click="revealPrivateKey = !revealPrivateKey">
154
+ {{ revealPrivateKey ? 'Hide' : 'Reveal' }}
155
+ </button>
156
+ </div>
157
+ </div>
158
+
159
+ <div class="warn">
160
+ Shown here only to demonstrate the SDK. A real application should never display
161
+ or log a private key. The SDK keeps it out of harm's way by default —
162
+ <code>JSON.stringify(keypair)</code> emits only the public key.
163
+ </div>
164
+
165
+ <p class="hint">
166
+ Changing the chain number only re-encodes the address, since the chain is stamped
167
+ alongside the account id. Record type and passphrase feed the derivation path
168
+ instead, so changing either re-derives the key and the account id with it — which
169
+ is how one phrase yields different addresses in two apps. Coin type is fixed
170
+ at <code>mainnet</code> here.
171
+ </p>
172
+ </template>
173
+ </section>
174
+ </template>
175
+
176
+ <style scoped>
177
+ .ctl { display: inline-flex; align-items: center; gap: 8px; }
178
+ .ctl input, .ctl select { width: 140px; }
179
+ /* An empty passphrase is a real setting, not a missing one — say so rather than
180
+ letting a grey placeholder read as a chosen value. */
181
+ .ctl input.unset { border-style: dashed; }
182
+ </style>
@@ -0,0 +1,105 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import { generateMnemonic, validateMnemonic } from '@geeqdev/geeq-sdk'
4
+ import type { MnemonicWordCount } from '@geeqdev/geeq-sdk'
5
+ import CopyButton from './CopyButton.vue'
6
+
7
+ const emit = defineEmits<{ (e: 'change', mnemonic: string): void }>()
8
+
9
+ const mode = ref<'create' | 'restore'>('create')
10
+ // Fixed, deliberately not exposed. The SDK supports 12/15/18/21/24, but the Geeq
11
+ // apps this demo mirrors issue 12-word phrases, so offering a choice here would let
12
+ // someone generate a phrase those apps cannot produce.
13
+ const WORD_COUNT: MnemonicWordCount = 12
14
+ const phrase = ref('')
15
+ const restoreInput = ref('')
16
+
17
+ const words = computed(() => (phrase.value ? phrase.value.split(' ') : []))
18
+
19
+ // Pure JavaScript in the SDK — no engine needed, so this is instant and works
20
+ // even while the WebAssembly engine is still downloading.
21
+ function create() {
22
+ phrase.value = generateMnemonic({ words: WORD_COUNT })
23
+ emit('change', phrase.value)
24
+ }
25
+
26
+ const restoreState = computed(() => {
27
+ if (restoreInput.value.trim() === '') return 'empty'
28
+ return validateMnemonic(restoreInput.value) ? 'valid' : 'invalid'
29
+ })
30
+
31
+ function useRestored() {
32
+ phrase.value = restoreInput.value.trim().replace(/\s+/gu, ' ').toLowerCase()
33
+ emit('change', phrase.value)
34
+ }
35
+
36
+ function switchMode(next: 'create' | 'restore') {
37
+ mode.value = next
38
+ phrase.value = ''
39
+ emit('change', '')
40
+ }
41
+ </script>
42
+
43
+ <template>
44
+ <section class="card">
45
+ <h2><span class="step">1</span>Recovery phrase</h2>
46
+
47
+ <div class="row">
48
+ <button :class="{ primary: mode === 'create' }" @click="switchMode('create')">
49
+ Create new
50
+ </button>
51
+ <button :class="{ primary: mode === 'restore' }" @click="switchMode('restore')">
52
+ Restore existing
53
+ </button>
54
+ </div>
55
+
56
+ <template v-if="mode === 'create'">
57
+ <div class="row" style="margin-top: 16px">
58
+ <button class="primary" @click="create">Generate phrase</button>
59
+ <CopyButton v-if="phrase" :value="phrase" />
60
+ </div>
61
+
62
+ <div v-if="words.length" class="phrase" style="margin-top: 16px">
63
+ <div v-for="(word, i) in words" :key="i" class="word">
64
+ <span class="n">{{ i + 1 }}</span><span>{{ word }}</span>
65
+ </div>
66
+ </div>
67
+ <p v-else class="hint">
68
+ Twelve words, generated with <code>generateMnemonic()</code> using the platform's
69
+ secure random source. No engine required, so this works instantly.
70
+ </p>
71
+
72
+ <div v-if="phrase" class="warn">
73
+ <strong>Write these words down in order.</strong>
74
+ They are the only way to recover your account. Anyone who reads them controls it.
75
+ This demo keeps them in memory only — nothing is saved or sent anywhere.
76
+ </div>
77
+ </template>
78
+
79
+ <template v-else>
80
+ <div style="margin-top: 16px">
81
+ <textarea
82
+ v-model="restoreInput"
83
+ spellcheck="false"
84
+ autocapitalize="none"
85
+ placeholder="Enter your 12-word recovery phrase, separated by spaces"
86
+ />
87
+ <div class="row" style="margin-top: 10px">
88
+ <span
89
+ class="hint grow"
90
+ :class="{ valid: restoreState === 'valid', invalid: restoreState === 'invalid' }"
91
+ >
92
+ <template v-if="restoreState === 'empty'">
93
+ Checked with <code>validateMnemonic()</code> as you type.
94
+ </template>
95
+ <template v-else-if="restoreState === 'valid'">Valid phrase.</template>
96
+ <template v-else>Not a valid phrase — check the words and their order.</template>
97
+ </span>
98
+ <button class="primary" :disabled="restoreState !== 'valid'" @click="useRestored">
99
+ Use this phrase
100
+ </button>
101
+ </div>
102
+ </div>
103
+ </template>
104
+ </section>
105
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import type { SocketConnection } from '../composables/useSocketConnection'
3
+
4
+ defineProps<{ connection: SocketConnection }>()
5
+ </script>
6
+
7
+ <template>
8
+ <div v-if="connection.state === 'failed'" class="warn" data-testid="socket-warning">
9
+ <strong>No confirmation will follow.</strong> The node socket did not open, so you can
10
+ still submit, but nothing will tell you what the chain decided. {{ connection.error }}
11
+ <div class="row" style="margin-top: 8px">
12
+ <button class="small" data-testid="socket-retry" @click="connection.retry()">Retry</button>
13
+ </div>
14
+ </div>
15
+ <p v-else-if="connection.state === 'reconnecting'" class="hint">
16
+ The node socket is reconnecting. Anything the node reports meanwhile is lost.
17
+ </p>
18
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Holds a step until the steps it builds on are done.
4
+ *
5
+ * A held step stays on the page, so what it does can be read ahead, but nothing in it can
6
+ * be used: `inert` takes every control out of reach, keyboard included. The note above it
7
+ * names the step to do first.
8
+ */
9
+ defineProps<{
10
+ /** What has to happen first, or null once nothing does. */
11
+ waitingFor?: string | null
12
+ /** Whether this step's own work is done, which marks its number. */
13
+ done?: boolean
14
+ }>()
15
+ </script>
16
+
17
+ <template>
18
+ <div class="gate" :class="{ locked: !!waitingFor, done }">
19
+ <p v-if="waitingFor" class="gate-note" data-testid="step-locked">{{ waitingFor }}</p>
20
+ <div :inert="!!waitingFor">
21
+ <slot />
22
+ </div>
23
+ </div>
24
+ </template>