@arkade-os/sdk 0.4.0-next.0 → 0.4.0-next.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 (56) hide show
  1. package/README.md +127 -24
  2. package/dist/cjs/bip322/index.js +270 -0
  3. package/dist/cjs/index.js +4 -2
  4. package/dist/cjs/intent/index.js +19 -9
  5. package/dist/cjs/repositories/indexedDB/contractRepository.js +1 -1
  6. package/dist/cjs/repositories/indexedDB/db.js +8 -46
  7. package/dist/cjs/repositories/indexedDB/walletRepository.js +1 -1
  8. package/dist/cjs/repositories/realm/contractRepository.js +120 -0
  9. package/dist/cjs/repositories/realm/index.js +9 -0
  10. package/dist/cjs/repositories/realm/schemas.js +108 -0
  11. package/dist/cjs/repositories/realm/types.js +7 -0
  12. package/dist/cjs/repositories/realm/walletRepository.js +273 -0
  13. package/dist/cjs/repositories/serialization.js +49 -0
  14. package/dist/cjs/repositories/sqlite/contractRepository.js +139 -0
  15. package/dist/cjs/repositories/sqlite/index.js +7 -0
  16. package/dist/cjs/repositories/sqlite/types.js +2 -0
  17. package/dist/cjs/repositories/sqlite/walletRepository.js +328 -0
  18. package/dist/cjs/wallet/vtxo-manager.js +7 -0
  19. package/dist/esm/bip322/index.js +267 -0
  20. package/dist/esm/index.js +4 -1
  21. package/dist/esm/intent/index.js +16 -7
  22. package/dist/esm/repositories/indexedDB/contractRepository.js +1 -1
  23. package/dist/esm/repositories/indexedDB/db.js +2 -40
  24. package/dist/esm/repositories/indexedDB/walletRepository.js +1 -1
  25. package/dist/esm/repositories/realm/contractRepository.js +116 -0
  26. package/dist/esm/repositories/realm/index.js +3 -0
  27. package/dist/esm/repositories/realm/schemas.js +105 -0
  28. package/dist/esm/repositories/realm/types.js +6 -0
  29. package/dist/esm/repositories/realm/walletRepository.js +269 -0
  30. package/dist/esm/repositories/serialization.js +40 -0
  31. package/dist/esm/repositories/sqlite/contractRepository.js +135 -0
  32. package/dist/esm/repositories/sqlite/index.js +2 -0
  33. package/dist/esm/repositories/sqlite/types.js +1 -0
  34. package/dist/esm/repositories/sqlite/walletRepository.js +324 -0
  35. package/dist/esm/wallet/vtxo-manager.js +7 -0
  36. package/dist/types/bip322/index.d.ts +55 -0
  37. package/dist/types/index.d.ts +3 -2
  38. package/dist/types/intent/index.d.ts +13 -0
  39. package/dist/types/repositories/indexedDB/db.d.ts +2 -54
  40. package/dist/types/repositories/realm/contractRepository.d.ts +24 -0
  41. package/dist/types/repositories/realm/index.d.ts +4 -0
  42. package/dist/types/repositories/realm/schemas.d.ts +208 -0
  43. package/dist/types/repositories/realm/types.d.ts +16 -0
  44. package/dist/types/repositories/realm/walletRepository.d.ts +31 -0
  45. package/dist/types/repositories/serialization.d.ts +40 -0
  46. package/dist/types/repositories/sqlite/contractRepository.d.ts +33 -0
  47. package/dist/types/repositories/sqlite/index.d.ts +3 -0
  48. package/dist/types/repositories/sqlite/types.d.ts +18 -0
  49. package/dist/types/repositories/sqlite/walletRepository.d.ts +40 -0
  50. package/package.json +18 -14
  51. package/dist/cjs/adapters/expo-db.js +0 -35
  52. package/dist/esm/adapters/expo-db.js +0 -27
  53. package/dist/types/adapters/expo-db.d.ts +0 -7
  54. /package/dist/cjs/{db → repositories/indexedDB}/manager.js +0 -0
  55. /package/dist/esm/{db → repositories/indexedDB}/manager.js +0 -0
  56. /package/dist/types/{db → repositories/indexedDB}/manager.d.ts +0 -0
@@ -0,0 +1,269 @@
1
+ import { serializeVtxo, serializeUtxo, deserializeVtxo, deserializeUtxo, } from '../serialization.js';
2
+ /**
3
+ * Realm-based implementation of WalletRepository.
4
+ *
5
+ * Consumers must open Realm with the schemas from `./schemas.ts` and pass
6
+ * the instance to the constructor.
7
+ *
8
+ * Realm handles schema creation on open, so `ensureInit()` is a no-op.
9
+ * The consumer owns the Realm lifecycle — `[Symbol.asyncDispose]` is a no-op.
10
+ */
11
+ export class RealmWalletRepository {
12
+ constructor(realm) {
13
+ this.realm = realm;
14
+ this.version = 1;
15
+ }
16
+ // ── Lifecycle ──────────────────────────────────────────────────────
17
+ async ensureInit() {
18
+ // Realm handles schema on open — nothing to initialise.
19
+ }
20
+ async [Symbol.asyncDispose]() {
21
+ // no-op — consumer owns the Realm lifecycle
22
+ }
23
+ // ── Clear ──────────────────────────────────────────────────────────
24
+ async clear() {
25
+ await this.ensureInit();
26
+ this.realm.write(() => {
27
+ this.realm.delete(this.realm.objects("ArkVtxo"));
28
+ this.realm.delete(this.realm.objects("ArkUtxo"));
29
+ this.realm.delete(this.realm.objects("ArkTransaction"));
30
+ this.realm.delete(this.realm.objects("ArkWalletState"));
31
+ });
32
+ }
33
+ // ── VTXO management ────────────────────────────────────────────────
34
+ async getVtxos(address) {
35
+ await this.ensureInit();
36
+ const results = this.realm
37
+ .objects("ArkVtxo")
38
+ .filtered("address == $0", address);
39
+ return [...results].map(vtxoObjectToDomain);
40
+ }
41
+ async saveVtxos(address, vtxos) {
42
+ await this.ensureInit();
43
+ this.realm.write(() => {
44
+ for (const vtxo of vtxos) {
45
+ const s = serializeVtxo(vtxo);
46
+ this.realm.create("ArkVtxo", {
47
+ pk: `${s.txid}:${s.vout}`,
48
+ address,
49
+ txid: s.txid,
50
+ vout: s.vout,
51
+ value: s.value,
52
+ tapTree: s.tapTree,
53
+ forfeitCb: s.forfeitTapLeafScript.cb,
54
+ forfeitS: s.forfeitTapLeafScript.s,
55
+ intentCb: s.intentTapLeafScript.cb,
56
+ intentS: s.intentTapLeafScript.s,
57
+ statusJson: JSON.stringify(s.status),
58
+ virtualStatusJson: JSON.stringify(s.virtualStatus),
59
+ createdAt: typeof s.createdAt === "string"
60
+ ? s.createdAt
61
+ : s.createdAt instanceof Date
62
+ ? s.createdAt.toISOString()
63
+ : new Date(s.createdAt).toISOString(),
64
+ isUnrolled: s.isUnrolled ?? false,
65
+ isSpent: s.isSpent === undefined ? null : s.isSpent,
66
+ spentBy: s.spentBy ?? null,
67
+ settledBy: s.settledBy ?? null,
68
+ arkTxId: s.arkTxId ?? null,
69
+ extraWitnessJson: s.extraWitness
70
+ ? JSON.stringify(s.extraWitness)
71
+ : null,
72
+ assetsJson: s.assets ? JSON.stringify(s.assets) : null,
73
+ }, "modified");
74
+ }
75
+ });
76
+ }
77
+ async deleteVtxos(address) {
78
+ await this.ensureInit();
79
+ this.realm.write(() => {
80
+ const toDelete = this.realm
81
+ .objects("ArkVtxo")
82
+ .filtered("address == $0", address);
83
+ this.realm.delete(toDelete);
84
+ });
85
+ }
86
+ // ── UTXO management ────────────────────────────────────────────────
87
+ async getUtxos(address) {
88
+ await this.ensureInit();
89
+ const results = this.realm
90
+ .objects("ArkUtxo")
91
+ .filtered("address == $0", address);
92
+ return [...results].map(utxoObjectToDomain);
93
+ }
94
+ async saveUtxos(address, utxos) {
95
+ await this.ensureInit();
96
+ this.realm.write(() => {
97
+ for (const utxo of utxos) {
98
+ const s = serializeUtxo(utxo);
99
+ this.realm.create("ArkUtxo", {
100
+ pk: `${s.txid}:${s.vout}`,
101
+ address,
102
+ txid: s.txid,
103
+ vout: s.vout,
104
+ value: s.value,
105
+ tapTree: s.tapTree,
106
+ forfeitCb: s.forfeitTapLeafScript.cb,
107
+ forfeitS: s.forfeitTapLeafScript.s,
108
+ intentCb: s.intentTapLeafScript.cb,
109
+ intentS: s.intentTapLeafScript.s,
110
+ statusJson: JSON.stringify(s.status),
111
+ extraWitnessJson: s.extraWitness
112
+ ? JSON.stringify(s.extraWitness)
113
+ : null,
114
+ }, "modified");
115
+ }
116
+ });
117
+ }
118
+ async deleteUtxos(address) {
119
+ await this.ensureInit();
120
+ this.realm.write(() => {
121
+ const toDelete = this.realm
122
+ .objects("ArkUtxo")
123
+ .filtered("address == $0", address);
124
+ this.realm.delete(toDelete);
125
+ });
126
+ }
127
+ // ── Transaction history ────────────────────────────────────────────
128
+ async getTransactionHistory(address) {
129
+ await this.ensureInit();
130
+ const results = this.realm
131
+ .objects("ArkTransaction")
132
+ .filtered("address == $0", address);
133
+ const txs = [...results].map(txObjectToDomain);
134
+ txs.sort((a, b) => a.createdAt - b.createdAt);
135
+ return txs;
136
+ }
137
+ async saveTransactions(address, txs) {
138
+ await this.ensureInit();
139
+ this.realm.write(() => {
140
+ for (const tx of txs) {
141
+ this.realm.create("ArkTransaction", {
142
+ pk: `${address}:${tx.key.boardingTxid}:${tx.key.commitmentTxid}:${tx.key.arkTxid}`,
143
+ address,
144
+ boardingTxid: tx.key.boardingTxid,
145
+ commitmentTxid: tx.key.commitmentTxid,
146
+ arkTxid: tx.key.arkTxid,
147
+ type: tx.type,
148
+ amount: tx.amount,
149
+ settled: tx.settled,
150
+ createdAt: tx.createdAt,
151
+ assetsJson: tx.assets
152
+ ? JSON.stringify(tx.assets)
153
+ : null,
154
+ }, "modified");
155
+ }
156
+ });
157
+ }
158
+ async deleteTransactions(address) {
159
+ await this.ensureInit();
160
+ this.realm.write(() => {
161
+ const toDelete = this.realm
162
+ .objects("ArkTransaction")
163
+ .filtered("address == $0", address);
164
+ this.realm.delete(toDelete);
165
+ });
166
+ }
167
+ // ── Wallet state ───────────────────────────────────────────────────
168
+ async getWalletState() {
169
+ await this.ensureInit();
170
+ const results = this.realm
171
+ .objects("ArkWalletState")
172
+ .filtered("key == $0", "state");
173
+ const items = [...results];
174
+ if (items.length === 0)
175
+ return null;
176
+ const obj = items[0];
177
+ const state = {};
178
+ if (obj.lastSyncTime !== null && obj.lastSyncTime !== undefined) {
179
+ state.lastSyncTime = obj.lastSyncTime;
180
+ }
181
+ if (obj.settingsJson) {
182
+ state.settings = JSON.parse(obj.settingsJson);
183
+ }
184
+ return state;
185
+ }
186
+ async saveWalletState(state) {
187
+ await this.ensureInit();
188
+ this.realm.write(() => {
189
+ this.realm.create("ArkWalletState", {
190
+ key: "state",
191
+ lastSyncTime: state.lastSyncTime ?? null,
192
+ settingsJson: state.settings
193
+ ? JSON.stringify(state.settings)
194
+ : null,
195
+ }, "modified");
196
+ });
197
+ }
198
+ }
199
+ // ── Realm object → Domain converters ─────────────────────────────────────
200
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
+ function vtxoObjectToDomain(obj) {
202
+ const serialized = {
203
+ txid: obj.txid,
204
+ vout: obj.vout,
205
+ value: obj.value,
206
+ tapTree: obj.tapTree,
207
+ forfeitTapLeafScript: {
208
+ cb: obj.forfeitCb,
209
+ s: obj.forfeitS,
210
+ },
211
+ intentTapLeafScript: {
212
+ cb: obj.intentCb,
213
+ s: obj.intentS,
214
+ },
215
+ status: JSON.parse(obj.statusJson),
216
+ virtualStatus: JSON.parse(obj.virtualStatusJson),
217
+ createdAt: new Date(obj.createdAt),
218
+ isUnrolled: obj.isUnrolled,
219
+ isSpent: obj.isSpent === null ? undefined : obj.isSpent,
220
+ spentBy: obj.spentBy ?? undefined,
221
+ settledBy: obj.settledBy ?? undefined,
222
+ arkTxId: obj.arkTxId ?? undefined,
223
+ extraWitness: obj.extraWitnessJson
224
+ ? JSON.parse(obj.extraWitnessJson)
225
+ : undefined,
226
+ assets: obj.assetsJson ? JSON.parse(obj.assetsJson) : undefined,
227
+ };
228
+ return deserializeVtxo(serialized);
229
+ }
230
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
231
+ function utxoObjectToDomain(obj) {
232
+ const serialized = {
233
+ txid: obj.txid,
234
+ vout: obj.vout,
235
+ value: obj.value,
236
+ tapTree: obj.tapTree,
237
+ forfeitTapLeafScript: {
238
+ cb: obj.forfeitCb,
239
+ s: obj.forfeitS,
240
+ },
241
+ intentTapLeafScript: {
242
+ cb: obj.intentCb,
243
+ s: obj.intentS,
244
+ },
245
+ status: JSON.parse(obj.statusJson),
246
+ extraWitness: obj.extraWitnessJson
247
+ ? JSON.parse(obj.extraWitnessJson)
248
+ : undefined,
249
+ };
250
+ return deserializeUtxo(serialized);
251
+ }
252
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
253
+ function txObjectToDomain(obj) {
254
+ const tx = {
255
+ key: {
256
+ boardingTxid: obj.boardingTxid,
257
+ commitmentTxid: obj.commitmentTxid,
258
+ arkTxid: obj.arkTxid,
259
+ },
260
+ type: obj.type,
261
+ amount: obj.amount,
262
+ settled: obj.settled,
263
+ createdAt: obj.createdAt,
264
+ };
265
+ if (obj.assetsJson) {
266
+ tx.assets = JSON.parse(obj.assetsJson);
267
+ }
268
+ return tx;
269
+ }
@@ -0,0 +1,40 @@
1
+ import { hex } from "@scure/base";
2
+ import { TaprootControlBlock } from "@scure/btc-signer";
3
+ export const serializeTapLeaf = ([cb, s,]) => ({
4
+ cb: hex.encode(TaprootControlBlock.encode(cb)),
5
+ s: hex.encode(s),
6
+ });
7
+ export const serializeVtxo = (v) => ({
8
+ ...v,
9
+ tapTree: hex.encode(v.tapTree),
10
+ forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
11
+ intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
12
+ extraWitness: v.extraWitness?.map(hex.encode),
13
+ });
14
+ export const serializeUtxo = (u) => ({
15
+ ...u,
16
+ tapTree: hex.encode(u.tapTree),
17
+ forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
18
+ intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
19
+ extraWitness: u.extraWitness?.map(hex.encode),
20
+ });
21
+ export const deserializeTapLeaf = (t) => {
22
+ const cb = TaprootControlBlock.decode(hex.decode(t.cb));
23
+ const s = hex.decode(t.s);
24
+ return [cb, s];
25
+ };
26
+ export const deserializeVtxo = (o) => ({
27
+ ...o,
28
+ createdAt: new Date(o.createdAt),
29
+ tapTree: hex.decode(o.tapTree),
30
+ forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
31
+ intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
32
+ extraWitness: o.extraWitness?.map(hex.decode),
33
+ });
34
+ export const deserializeUtxo = (o) => ({
35
+ ...o,
36
+ tapTree: hex.decode(o.tapTree),
37
+ forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
38
+ intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
39
+ extraWitness: o.extraWitness?.map(hex.decode),
40
+ });
@@ -0,0 +1,135 @@
1
+ /**
2
+ * SQLite-based implementation of ContractRepository.
3
+ *
4
+ * Uses the SQLExecutor interface so consumers can plug in any SQLite driver
5
+ * (expo-sqlite, better-sqlite3, etc.).
6
+ *
7
+ * Tables are created lazily on first operation via `ensureInit()`.
8
+ * The consumer owns the SQLExecutor lifecycle — `[Symbol.asyncDispose]` is a no-op.
9
+ */
10
+ export class SQLiteContractRepository {
11
+ constructor(db, options) {
12
+ this.db = db;
13
+ this.version = 1;
14
+ this.initPromise = null;
15
+ this.prefix = sanitizePrefix(options?.prefix ?? "ark_");
16
+ this.table = `${this.prefix}contracts`;
17
+ }
18
+ // ── Lifecycle ──────────────────────────────────────────────────────
19
+ ensureInit() {
20
+ if (!this.initPromise) {
21
+ this.initPromise = this.init();
22
+ }
23
+ return this.initPromise;
24
+ }
25
+ async init() {
26
+ await this.db.run(`
27
+ CREATE TABLE IF NOT EXISTS ${this.table} (
28
+ script TEXT PRIMARY KEY,
29
+ address TEXT NOT NULL,
30
+ type TEXT NOT NULL,
31
+ state TEXT NOT NULL,
32
+ params_json TEXT NOT NULL,
33
+ created_at INTEGER NOT NULL,
34
+ expires_at INTEGER,
35
+ label TEXT,
36
+ metadata_json TEXT
37
+ )
38
+ `);
39
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_${this.prefix}contracts_type ON ${this.table} (type)`);
40
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_${this.prefix}contracts_state ON ${this.table} (state)`);
41
+ }
42
+ async [Symbol.asyncDispose]() {
43
+ // no-op — consumer owns the SQLExecutor lifecycle
44
+ }
45
+ // ── Clear ──────────────────────────────────────────────────────────
46
+ async clear() {
47
+ await this.ensureInit();
48
+ await this.db.run(`DELETE FROM ${this.table}`);
49
+ }
50
+ // ── Contract management ────────────────────────────────────────────
51
+ async getContracts(filter) {
52
+ await this.ensureInit();
53
+ const conditions = [];
54
+ const params = [];
55
+ if (filter) {
56
+ this.addFilterCondition(conditions, params, "script", filter.script);
57
+ this.addFilterCondition(conditions, params, "state", filter.state);
58
+ this.addFilterCondition(conditions, params, "type", filter.type);
59
+ }
60
+ let sql = `SELECT * FROM ${this.table}`;
61
+ if (conditions.length > 0) {
62
+ sql += ` WHERE ${conditions.join(" AND ")}`;
63
+ }
64
+ const rows = await this.db.all(sql, params);
65
+ return rows.map(contractRowToDomain);
66
+ }
67
+ async saveContract(contract) {
68
+ await this.ensureInit();
69
+ await this.db.run(`INSERT OR REPLACE INTO ${this.table}
70
+ (script, address, type, state, params_json,
71
+ created_at, expires_at, label, metadata_json)
72
+ VALUES (?, ?, ?, ?, ?,
73
+ ?, ?, ?, ?)`, [
74
+ contract.script,
75
+ contract.address,
76
+ contract.type,
77
+ contract.state,
78
+ JSON.stringify(contract.params),
79
+ contract.createdAt,
80
+ contract.expiresAt ?? null,
81
+ contract.label ?? null,
82
+ contract.metadata ? JSON.stringify(contract.metadata) : null,
83
+ ]);
84
+ }
85
+ async deleteContract(script) {
86
+ await this.ensureInit();
87
+ await this.db.run(`DELETE FROM ${this.table} WHERE script = ?`, [
88
+ script,
89
+ ]);
90
+ }
91
+ // ── Helpers ─────────────────────────────────────────────────────────
92
+ addFilterCondition(conditions, params, column, value) {
93
+ if (value === undefined)
94
+ return;
95
+ if (Array.isArray(value)) {
96
+ if (value.length === 0)
97
+ return;
98
+ const placeholders = value.map(() => "?").join(", ");
99
+ conditions.push(`${column} IN (${placeholders})`);
100
+ params.push(...value);
101
+ }
102
+ else {
103
+ conditions.push(`${column} = ?`);
104
+ params.push(value);
105
+ }
106
+ }
107
+ }
108
+ // ── Row → Domain converter ──────────────────────────────────────────────
109
+ const SAFE_PREFIX = /^[a-zA-Z0-9_]+$/;
110
+ function sanitizePrefix(prefix) {
111
+ if (!SAFE_PREFIX.test(prefix)) {
112
+ throw new Error(`Invalid table prefix "${prefix}": only letters, digits, and underscores are allowed`);
113
+ }
114
+ return prefix;
115
+ }
116
+ function contractRowToDomain(row) {
117
+ const contract = {
118
+ script: row.script,
119
+ address: row.address,
120
+ type: row.type,
121
+ state: row.state,
122
+ params: JSON.parse(row.params_json),
123
+ createdAt: row.created_at,
124
+ };
125
+ if (row.expires_at !== null) {
126
+ contract.expiresAt = row.expires_at;
127
+ }
128
+ if (row.label !== null) {
129
+ contract.label = row.label;
130
+ }
131
+ if (row.metadata_json !== null) {
132
+ contract.metadata = JSON.parse(row.metadata_json);
133
+ }
134
+ return contract;
135
+ }
@@ -0,0 +1,2 @@
1
+ export { SQLiteWalletRepository } from './walletRepository.js';
2
+ export { SQLiteContractRepository } from './contractRepository.js';
@@ -0,0 +1 @@
1
+ export {};