Skip to content

Commit 19276fe

Browse files
authored
Merge pull request #321 from arkade-os/node-sqlite-example
Node sqlite example
2 parents 08c5708 + 6c2a7e8 commit 19276fe

4 files changed

Lines changed: 147 additions & 56 deletions

File tree

README.md

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -707,25 +707,69 @@ class MyWalletRepository implements WalletRepository {
707707
}
708708
```
709709
710-
Note: `IndexedDB*Repository` requires [indexeddbshim](https://github.com/indexeddbshim/indexeddbshim) in Node or other
711-
**non-browser environments**. It is a dev dependency of the SDK, so you must
712-
install and initialize it in your app before using the repositories. This
713-
also applies when you rely on the default storage behavior (no `storage`).
710+
#### SQLite Repository (Node.js / React Native)
714711

715-
Please see the working example in [examples/node/multiple-wallets.ts](examples/node/multiple-wallets.ts).
712+
For Node.js or React Native environments, use the SQLite repository with any
713+
SQLite driver. The SDK accepts a `SQLExecutor` interface — you provide the
714+
driver, the SDK handles the schema.
715+
716+
See [examples/node/multiple-wallets.ts](examples/node/multiple-wallets.ts) for
717+
a full working example using `better-sqlite3`.
716718

717719
```typescript
718720
import { SingleKey, Wallet } from '@arkade-os/sdk'
719-
import setGlobalVars from 'indexeddbshim'
721+
import { SQLiteWalletRepository, SQLiteContractRepository, SQLExecutor } from '@arkade-os/sdk/repositories/sqlite'
722+
import Database from 'better-sqlite3'
720723

721-
setGlobalVars()
724+
const db = new Database('my-wallet.sqlite')
725+
db.pragma('journal_mode = WAL')
722726

723-
const identity = SingleKey.fromHex('your_private_key_hex')
727+
const executor: SQLExecutor = {
728+
run: async (sql, params) => { db.prepare(sql).run(...(params ?? [])) },
729+
get: async (sql, params) => db.prepare(sql).get(...(params ?? [])) as any,
730+
all: async (sql, params) => db.prepare(sql).all(...(params ?? [])) as any,
731+
}
724732

725-
// Create wallet with default IndexedDB storage
733+
const wallet = await Wallet.create({
734+
identity: SingleKey.fromHex('your_private_key_hex'),
735+
arkServerUrl: 'https://mutinynet.arkade.sh',
736+
storage: {
737+
walletRepository: new SQLiteWalletRepository(executor),
738+
contractRepository: new SQLiteContractRepository(executor),
739+
},
740+
})
741+
```
742+
743+
#### Realm Repository (React Native)
744+
745+
For React Native apps using Realm, pass your Realm instance directly:
746+
747+
```typescript
748+
import { RealmWalletRepository, RealmContractRepository, ArkRealmSchemas } from '@arkade-os/sdk/repositories/realm'
749+
750+
const realm = await Realm.open({ schema: [...ArkRealmSchemas, ...yourSchemas] })
726751
const wallet = await Wallet.create({
727752
identity,
728753
arkServerUrl: 'https://mutinynet.arkade.sh',
754+
storage: {
755+
walletRepository: new RealmWalletRepository(realm),
756+
contractRepository: new RealmContractRepository(realm),
757+
},
758+
})
759+
```
760+
761+
#### IndexedDB Repository (Browser)
762+
763+
In the browser, the SDK defaults to IndexedDB repositories when no `storage`
764+
is provided:
765+
766+
```typescript
767+
import { SingleKey, Wallet } from '@arkade-os/sdk'
768+
769+
const wallet = await Wallet.create({
770+
identity: SingleKey.fromHex('your_private_key_hex'),
771+
arkServerUrl: 'https://mutinynet.arkade.sh',
772+
// Uses IndexedDB by default in the browser
729773
})
730774
```
731775

@@ -813,26 +857,31 @@ Both ExpoArkProvider and ExpoIndexerProvider are available as adapters following
813857
- **ExpoArkProvider**: Handles settlement events and transaction streaming using expo/fetch for Server-Sent Events
814858
- **ExpoIndexerProvider**: Handles address subscriptions and VTXO updates using expo/fetch for JSON streaming
815859

816-
To use IndexedDB repositories in Expo/React Native, call `setupExpoDb()` before any SDK import.
817-
This sets up `indexeddbshim` backed by expo-sqlite under the hood:
860+
For persistence in Expo/React Native, use the SQLite repository with `expo-sqlite`:
818861

819862
```typescript
820-
import { setupExpoDb } from '@arkade-os/sdk/adapters/expo-db';
863+
import { SQLiteWalletRepository, SQLiteContractRepository } from '@arkade-os/sdk/repositories/sqlite'
864+
import * as SQLite from 'expo-sqlite'
865+
866+
const db = SQLite.openDatabaseSync('my-wallet.db')
867+
const executor = {
868+
run: (sql, params) => db.runAsync(sql, params ?? []),
869+
get: (sql, params) => db.getFirstAsync(sql, params ?? []),
870+
all: (sql, params) => db.getAllAsync(sql, params ?? []),
871+
}
821872

822-
setupExpoDb();
873+
const wallet = await Wallet.create({
874+
identity,
875+
arkServerUrl: 'https://mutinynet.arkade.sh',
876+
arkProvider: new ExpoArkProvider('https://mutinynet.arkade.sh'),
877+
indexerProvider: new ExpoIndexerProvider('https://mutinynet.arkade.sh'),
878+
storage: {
879+
walletRepository: new SQLiteWalletRepository(executor),
880+
contractRepository: new SQLiteContractRepository(executor),
881+
},
882+
})
823883
```
824884

825-
> **Note:** `setupExpoDb` accepts an optional `SetupExpoDbOptions` object to
826-
> customise `origin`, `checkOrigin`, and `cacheDatabaseInstances`.
827-
828-
> **Note:** `expo-sqlite` and `indexeddbshim` are optional peer dependencies,
829-
> only required when importing from `@arkade-os/sdk/adapters/expo-db`. The
830-
> streaming providers (`@arkade-os/sdk/adapters/expo`) have no expo-sqlite
831-
> dependency. Install them with:
832-
> ```bash
833-
> npx expo install expo-sqlite && npm install indexeddbshim
834-
> ```
835-
836885
#### Crypto Polyfill Requirement
837886

838887
Install `expo-crypto` and polyfill `crypto.getRandomValues()` at the top of your app entry point:

examples/node/multiple-wallets.ts

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,61 @@
11
/**
2-
* This example shows how to create two wallets using the SDK and onboard
3-
* Alice's wallet into the Ark protocol.
2+
* This example shows how to create two wallets using the SDK.
3+
* Alice's wallet will be persisted in SQLite, while Bob's wallet will be in-memory.
44
*
5-
* It demonstrates:
6-
* - Creating in-memory and IndexedDB-backed wallets
7-
* - Funding a boarding address via nigiri faucet
8-
* - Settling (onboarding) into the Ark protocol using Ramps
9-
*
10-
* Requires a local regtest environment (nigiri + Ark server on localhost:7070).
5+
* By inspecting the `alice-wallet.sqlite` file created upon running the code,
6+
* you can see the persisted data for Alice's wallet.
117
*
128
* To run it:
139
* ```
1410
* $ npx tsx examples/node/multiple-wallets.ts
1511
* ```
12+
*
13+
* Requires `better-sqlite3` (included as a devDependency).
1614
*/
1715

1816
import {
19-
IndexedDBContractRepository,
20-
IndexedDBWalletRepository,
2117
InMemoryContractRepository,
2218
InMemoryWalletRepository,
23-
Ramps,
2419
SingleKey,
2520
Wallet,
21+
Ramps,
2622
} from "../../src";
23+
import { WalletState } from "../../src/repositories";
24+
import {
25+
SQLiteWalletRepository,
26+
SQLiteContractRepository,
27+
SQLExecutor,
28+
} from "../../src/repositories/sqlite";
29+
import Database from "better-sqlite3";
30+
import { execSync } from "child_process";
2731

2832
// EventSource is used internally by the SDK for settlement events (SSE).
2933
// It is not available in Node.js by default, so we need to polyfill it.
3034
import { EventSource } from "eventsource";
3135
(globalThis as any).EventSource = EventSource;
3236

33-
// Must define `self` BEFORE calling setGlobalVars
34-
if (typeof self === "undefined") {
35-
(globalThis as any).self = globalThis;
36-
}
37-
import setGlobalVars from "indexeddbshim/src/node-UnicodeIdentifiers";
38-
import { execSync } from "child_process";
39-
40-
(globalThis as any).window = globalThis;
37+
function createSQLExecutor(dbPath: string): SQLExecutor {
38+
const db = new Database(dbPath);
39+
db.pragma("journal_mode = WAL");
4140

42-
setGlobalVars(null, { checkOrigin: false });
41+
return {
42+
run: async (sql, params) => {
43+
db.prepare(sql).run(...(params ?? []));
44+
},
45+
get: async <T>(sql: string, params?: unknown[]) =>
46+
db.prepare(sql).get(...(params ?? [])) as T | undefined,
47+
all: async <T>(sql: string, params?: unknown[]) =>
48+
db.prepare(sql).all(...(params ?? [])) as T[],
49+
};
50+
}
4351

4452
async function main() {
4553
console.log("Starting Ark SDK NodeJS Example...");
4654

4755
const bob = SingleKey.fromRandomBytes();
4856
const alice = SingleKey.fromRandomBytes();
4957

50-
// in-memory wallet
58+
// In-memory wallet
5159
const bobWallet = await Wallet.create({
5260
identity: bob,
5361
arkServerUrl: "http://localhost:7070",
@@ -61,20 +69,30 @@ async function main() {
6169
console.log("[Bob]\tWallet created successfully!");
6270
console.log("[Bob]\tArk Address:", bobWallet.arkAddress.encode());
6371

64-
// IndexedDB-backed wallet (persisted)
72+
// SQLite-persisted wallet
73+
const executor = createSQLExecutor("alice-wallet.sqlite");
74+
6575
const aliceWallet = await Wallet.create({
6676
identity: alice,
6777
arkServerUrl: "http://localhost:7070",
6878
esploraUrl: "http://localhost:3000",
6979
storage: {
70-
walletRepository: new IndexedDBWalletRepository(),
71-
contractRepository: new IndexedDBContractRepository(),
80+
walletRepository: new SQLiteWalletRepository(executor),
81+
contractRepository: new SQLiteContractRepository(executor),
7282
},
7383
});
7484

7585
console.log("[Alice]\tWallet created successfully!");
7686
console.log("[Alice]\tArk Address:", aliceWallet.arkAddress.encode());
7787

88+
const state: WalletState = {
89+
lastSyncTime: Date.now(),
90+
settings: { theme: "dark" },
91+
};
92+
93+
await aliceWallet.walletRepository.saveWalletState(state);
94+
await bobWallet.walletRepository.saveWalletState(state);
95+
7896
// Fund Alice's boarding address
7997
const boardingAddress = await aliceWallet.getBoardingAddress();
8098
console.log("[Alice]\tBoarding Address:", boardingAddress);
@@ -110,7 +128,7 @@ async function main() {
110128

111129
console.log("[Alice]\tBalance:", await aliceWallet.getBalance());
112130
console.log("[Bob]\tBalance:", await bobWallet.getBalance());
113-
console.log("Only Alice's data is persisted in IndexedDB");
131+
console.log("Only Alice's data is persisted on disk");
114132
}
115133

116134
main().catch(console.error);

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@
126126
"bip68": "1.0.4"
127127
},
128128
"devDependencies": {
129+
"@types/better-sqlite3": "7.6.13",
129130
"@types/node": "24.3.1",
130131
"@vitest/coverage-v8": "3.2.4",
132+
"better-sqlite3": "12.6.2",
131133
"esbuild": "^0.27.3",
132134
"eventsource": "4.0.0",
133135
"glob": "11.1.0",
@@ -140,11 +142,11 @@
140142
"vitest": "3.2.4"
141143
},
142144
"peerDependencies": {
145+
"@react-native-async-storage/async-storage": ">=1.0.0",
143146
"expo": ">=54.0.0",
144-
"expo-sqlite": "~16.0.10",
145147
"expo-background-task": "~1.0.10",
146-
"expo-task-manager": "~14.0.9",
147-
"@react-native-async-storage/async-storage": ">=1.0.0"
148+
"expo-sqlite": "~16.0.10",
149+
"expo-task-manager": "~14.0.9"
148150
},
149151
"peerDependenciesMeta": {
150152
"expo": {
@@ -186,7 +188,8 @@
186188
},
187189
"onlyBuiltDependencies": [
188190
"canvas",
189-
"sqlite3"
191+
"sqlite3",
192+
"better-sqlite3"
190193
]
191194
}
192195
}

pnpm-lock.yaml

Lines changed: 24 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)