@canton-network/core-wallet-store-sql 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 +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +64 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/migrator.d.ts +5 -0
- package/dist/migrator.d.ts.map +1 -0
- package/dist/migrator.js +64 -0
- package/dist/schema.d.ts +69 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +159 -0
- package/dist/store-sql.d.ts +30 -0
- package/dist/store-sql.d.ts.map +1 -0
- package/dist/store-sql.js +287 -0
- package/dist/store-sql.test.d.ts +2 -0
- package/dist/store-sql.test.d.ts.map +1 -0
- package/dist/store-sql.test.js +206 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# wallet-store
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAA;AAKpE,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAuEtD"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { connection, StoreSql } from './store-sql.js';
|
|
3
|
+
import { migrator } from './migrator.js';
|
|
4
|
+
import { pino } from 'pino';
|
|
5
|
+
const logger = pino({ name: 'main', level: 'debug' });
|
|
6
|
+
export function createCLI(config) {
|
|
7
|
+
console.log('Wallet Store Sql CLI');
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.command('up')
|
|
11
|
+
.description('Run all pending migrations')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const db = connection(config);
|
|
14
|
+
const umzug = migrator(db);
|
|
15
|
+
await umzug.up();
|
|
16
|
+
await db.destroy();
|
|
17
|
+
});
|
|
18
|
+
program
|
|
19
|
+
.command('down')
|
|
20
|
+
.description('Rollback last migration')
|
|
21
|
+
.action(async () => {
|
|
22
|
+
const db = connection(config);
|
|
23
|
+
const umzug = migrator(db);
|
|
24
|
+
await umzug.down();
|
|
25
|
+
await db.destroy();
|
|
26
|
+
});
|
|
27
|
+
program
|
|
28
|
+
.command('status')
|
|
29
|
+
.description('Show executed and pending migrations')
|
|
30
|
+
.action(async () => {
|
|
31
|
+
const db = connection(config);
|
|
32
|
+
const umzug = migrator(db);
|
|
33
|
+
const executed = await umzug.executed();
|
|
34
|
+
const pending = await umzug.pending();
|
|
35
|
+
console.log('Executed migrations:', executed);
|
|
36
|
+
console.log('Pending migrations:', pending);
|
|
37
|
+
await db.destroy();
|
|
38
|
+
});
|
|
39
|
+
program
|
|
40
|
+
.command('reset')
|
|
41
|
+
.description('Rollback all migrations and reapply them')
|
|
42
|
+
.action(async () => {
|
|
43
|
+
const db = connection(config);
|
|
44
|
+
const umzug = migrator(db);
|
|
45
|
+
const executed = await umzug.executed();
|
|
46
|
+
// Rollback all executed migrations in reverse order
|
|
47
|
+
for (const migration of executed.reverse()) {
|
|
48
|
+
await umzug.down({ to: migration.name });
|
|
49
|
+
}
|
|
50
|
+
// Reapply all migrations
|
|
51
|
+
await umzug.up();
|
|
52
|
+
await db.destroy();
|
|
53
|
+
});
|
|
54
|
+
program
|
|
55
|
+
.command('bootstrap')
|
|
56
|
+
.description('Bootstrap DB from config')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
const db = connection(config);
|
|
59
|
+
const store = new StoreSql(db, logger);
|
|
60
|
+
await Promise.all(config.networks.map((network) => store.addNetwork(network)));
|
|
61
|
+
await db.destroy();
|
|
62
|
+
});
|
|
63
|
+
return program;
|
|
64
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrator.d.ts","sourceRoot":"","sources":["../src/migrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA+B,MAAM,OAAO,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAA;AAwC7B,eAAO,MAAM,QAAQ,GAAI,IAAI,MAAM,CAAC,EAAE,CAAC,sBA0BtC,CAAA"}
|
package/dist/migrator.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Umzug } from 'umzug';
|
|
2
|
+
class KyselyStorage {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
async ensureTable() {
|
|
8
|
+
await this.db.schema
|
|
9
|
+
.createTable('migrations')
|
|
10
|
+
.ifNotExists()
|
|
11
|
+
.addColumn('name', 'text', (col) => col.primaryKey())
|
|
12
|
+
.addColumn('executedAt', 'text', (col) => col.notNull())
|
|
13
|
+
.execute();
|
|
14
|
+
}
|
|
15
|
+
async executed() {
|
|
16
|
+
await this.ensureTable();
|
|
17
|
+
const rows = await this.db
|
|
18
|
+
.selectFrom('migrations')
|
|
19
|
+
.select('name')
|
|
20
|
+
.execute();
|
|
21
|
+
return rows.map((r) => r.name);
|
|
22
|
+
}
|
|
23
|
+
async logMigration({ name }) {
|
|
24
|
+
await this.ensureTable();
|
|
25
|
+
await this.db
|
|
26
|
+
.insertInto('migrations')
|
|
27
|
+
.values({ name, executedAt: new Date().toISOString() })
|
|
28
|
+
.execute();
|
|
29
|
+
}
|
|
30
|
+
async unlogMigration({ name }) {
|
|
31
|
+
await this.ensureTable();
|
|
32
|
+
await this.db
|
|
33
|
+
.deleteFrom('migrations')
|
|
34
|
+
.where('name', '=', name)
|
|
35
|
+
.execute();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export const migrator = (db) => {
|
|
39
|
+
const ext = import.meta.url.endsWith('.ts') ? 'ts' : 'js';
|
|
40
|
+
const glob = new URL(`./migrations/*.${ext}`, import.meta.url).pathname;
|
|
41
|
+
return new Umzug({
|
|
42
|
+
migrations: {
|
|
43
|
+
glob: glob,
|
|
44
|
+
resolve: ({ name, path, context }) => {
|
|
45
|
+
// Dynamic import for ESM
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
up: async () => {
|
|
49
|
+
console.log(path);
|
|
50
|
+
const { up } = await import(path);
|
|
51
|
+
return up(context);
|
|
52
|
+
},
|
|
53
|
+
down: async () => {
|
|
54
|
+
const { down } = await import(path);
|
|
55
|
+
return down(context);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
context: db,
|
|
61
|
+
storage: new KyselyStorage(db),
|
|
62
|
+
logger: console,
|
|
63
|
+
});
|
|
64
|
+
};
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { UserId } from '@canton-network/core-wallet-auth';
|
|
2
|
+
import { Wallet, Transaction, Session, Auth, Network } from '@canton-network/core-wallet-store';
|
|
3
|
+
interface MigrationTable {
|
|
4
|
+
name: string;
|
|
5
|
+
executedAt: string;
|
|
6
|
+
}
|
|
7
|
+
interface IdpTable {
|
|
8
|
+
identityProviderId: string;
|
|
9
|
+
type: string;
|
|
10
|
+
issuer: string;
|
|
11
|
+
configUrl: string;
|
|
12
|
+
audience: string;
|
|
13
|
+
tokenUrl: string;
|
|
14
|
+
grantType: string;
|
|
15
|
+
scope: string;
|
|
16
|
+
clientId: string;
|
|
17
|
+
clientSecret: string;
|
|
18
|
+
adminClientId: string;
|
|
19
|
+
adminClientSecret: string;
|
|
20
|
+
}
|
|
21
|
+
interface NetworkTable {
|
|
22
|
+
name: string;
|
|
23
|
+
chainId: string;
|
|
24
|
+
synchronizerId: string;
|
|
25
|
+
description: string;
|
|
26
|
+
ledgerApiBaseUrl: string;
|
|
27
|
+
ledgerApiAdminGrpcUrl: string;
|
|
28
|
+
userId: UserId | undefined;
|
|
29
|
+
identityProviderId: string;
|
|
30
|
+
}
|
|
31
|
+
interface WalletTable {
|
|
32
|
+
primary: number;
|
|
33
|
+
partyId: string;
|
|
34
|
+
hint: string;
|
|
35
|
+
publicKey: string;
|
|
36
|
+
namespace: string;
|
|
37
|
+
chainId: string;
|
|
38
|
+
signingProviderId: string;
|
|
39
|
+
userId: UserId;
|
|
40
|
+
}
|
|
41
|
+
interface TransactionTable {
|
|
42
|
+
status: string;
|
|
43
|
+
commandId: string;
|
|
44
|
+
preparedTransaction: string;
|
|
45
|
+
preparedTransactionHash: string;
|
|
46
|
+
payload: string | undefined;
|
|
47
|
+
userId: UserId;
|
|
48
|
+
}
|
|
49
|
+
interface SessionTable extends Session {
|
|
50
|
+
userId: UserId;
|
|
51
|
+
}
|
|
52
|
+
export interface DB {
|
|
53
|
+
migrations: MigrationTable;
|
|
54
|
+
idps: IdpTable;
|
|
55
|
+
networks: NetworkTable;
|
|
56
|
+
wallets: WalletTable;
|
|
57
|
+
transactions: TransactionTable;
|
|
58
|
+
sessions: SessionTable;
|
|
59
|
+
}
|
|
60
|
+
export declare const toAuth: (table: IdpTable) => Auth;
|
|
61
|
+
export declare const fromAuth: (auth: Auth) => IdpTable;
|
|
62
|
+
export declare const toNetwork: (table: NetworkTable, authTable?: IdpTable) => Network;
|
|
63
|
+
export declare const fromNetwork: (network: Network, userId?: UserId) => NetworkTable;
|
|
64
|
+
export declare const fromWallet: (wallet: Wallet, userId: UserId) => WalletTable;
|
|
65
|
+
export declare const toWallet: (table: WalletTable) => Wallet;
|
|
66
|
+
export declare const fromTransaction: (transaction: Transaction, userId: UserId) => TransactionTable;
|
|
67
|
+
export declare const toTransaction: (table: TransactionTable) => Transaction;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAA;AACzD,OAAO,EACH,MAAM,EACN,WAAW,EACX,OAAO,EACP,IAAI,EACJ,OAAO,EACV,MAAM,mCAAmC,CAAA;AAE1C,UAAU,cAAc;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,QAAQ;IACd,kBAAkB,EAAE,MAAM,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;CAC5B;AAED,UAAU,YAAY;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,kBAAkB,EAAE,MAAM,CAAA;CAC7B;AAED,UAAU,WAAW;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,gBAAgB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,uBAAuB,EAAE,MAAM,CAAA;IAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,YAAa,SAAQ,OAAO;IAClC,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,EAAE;IACf,UAAU,EAAE,cAAc,CAAA;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,EAAE,WAAW,CAAA;IACpB,YAAY,EAAE,gBAAgB,CAAA;IAC9B,QAAQ,EAAE,YAAY,CAAA;CACzB;AAED,eAAO,MAAM,MAAM,GAAI,OAAO,QAAQ,KAAG,IAkDxC,CAAA;AAED,eAAO,MAAM,QAAQ,GAAI,MAAM,IAAI,KAAG,QAkDrC,CAAA;AAED,eAAO,MAAM,SAAS,GAClB,OAAO,YAAY,EACnB,YAAY,QAAQ,KACrB,OAeF,CAAA;AAED,eAAO,MAAM,WAAW,GACpB,SAAS,OAAO,EAChB,SAAS,MAAM,KAChB,YAWF,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,QAAQ,MAAM,KAAG,WAM3D,CAAA;AAED,eAAO,MAAM,QAAQ,GAAI,OAAO,WAAW,KAAG,MAK7C,CAAA;AAED,eAAO,MAAM,eAAe,GACxB,aAAa,WAAW,EACxB,QAAQ,MAAM,KACf,gBAQF,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,gBAAgB,KAAG,WAMvD,CAAA"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export const toAuth = (table) => {
|
|
2
|
+
switch (table.type) {
|
|
3
|
+
case 'password':
|
|
4
|
+
return {
|
|
5
|
+
identityProviderId: table.identityProviderId,
|
|
6
|
+
type: table.type,
|
|
7
|
+
issuer: table.issuer,
|
|
8
|
+
configUrl: table.configUrl,
|
|
9
|
+
audience: table.audience,
|
|
10
|
+
tokenUrl: table.tokenUrl || '',
|
|
11
|
+
grantType: table.grantType || '',
|
|
12
|
+
scope: table.scope,
|
|
13
|
+
clientId: table.clientId,
|
|
14
|
+
admin: {
|
|
15
|
+
clientId: table.adminClientId,
|
|
16
|
+
clientSecret: table.adminClientSecret,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
case 'implicit':
|
|
20
|
+
return {
|
|
21
|
+
identityProviderId: table.identityProviderId,
|
|
22
|
+
type: table.type,
|
|
23
|
+
issuer: table.issuer,
|
|
24
|
+
configUrl: table.configUrl,
|
|
25
|
+
audience: table.audience,
|
|
26
|
+
scope: table.scope,
|
|
27
|
+
clientId: table.clientId,
|
|
28
|
+
admin: {
|
|
29
|
+
clientId: table.adminClientId,
|
|
30
|
+
clientSecret: table.adminClientSecret,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
case 'client_credentials':
|
|
34
|
+
return {
|
|
35
|
+
identityProviderId: table.identityProviderId,
|
|
36
|
+
type: table.type,
|
|
37
|
+
issuer: table.issuer,
|
|
38
|
+
configUrl: table.configUrl,
|
|
39
|
+
audience: table.audience,
|
|
40
|
+
scope: table.scope,
|
|
41
|
+
clientId: table.clientId,
|
|
42
|
+
clientSecret: table.clientSecret,
|
|
43
|
+
admin: {
|
|
44
|
+
clientId: table.adminClientId,
|
|
45
|
+
clientSecret: table.adminClientSecret,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
default:
|
|
49
|
+
throw new Error(`Unknown auth type: ${table.type}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export const fromAuth = (auth) => {
|
|
53
|
+
switch (auth.type) {
|
|
54
|
+
case 'password':
|
|
55
|
+
return {
|
|
56
|
+
identityProviderId: auth.identityProviderId,
|
|
57
|
+
type: auth.type,
|
|
58
|
+
issuer: auth.issuer,
|
|
59
|
+
configUrl: auth.configUrl,
|
|
60
|
+
audience: auth.audience,
|
|
61
|
+
tokenUrl: auth.tokenUrl,
|
|
62
|
+
grantType: auth.grantType,
|
|
63
|
+
scope: auth.scope,
|
|
64
|
+
clientId: auth.clientId,
|
|
65
|
+
clientSecret: '',
|
|
66
|
+
adminClientId: auth.admin?.clientId || '',
|
|
67
|
+
adminClientSecret: auth.admin?.clientSecret || '',
|
|
68
|
+
};
|
|
69
|
+
case 'implicit':
|
|
70
|
+
return {
|
|
71
|
+
identityProviderId: auth.identityProviderId,
|
|
72
|
+
type: auth.type,
|
|
73
|
+
issuer: auth.issuer,
|
|
74
|
+
configUrl: auth.configUrl,
|
|
75
|
+
audience: auth.audience,
|
|
76
|
+
tokenUrl: '',
|
|
77
|
+
grantType: '',
|
|
78
|
+
scope: auth.scope,
|
|
79
|
+
clientId: auth.clientId,
|
|
80
|
+
clientSecret: '',
|
|
81
|
+
adminClientId: auth.admin?.clientId || '',
|
|
82
|
+
adminClientSecret: auth.admin?.clientSecret || '',
|
|
83
|
+
};
|
|
84
|
+
case 'client_credentials':
|
|
85
|
+
return {
|
|
86
|
+
identityProviderId: auth.identityProviderId,
|
|
87
|
+
type: auth.type,
|
|
88
|
+
issuer: auth.issuer,
|
|
89
|
+
configUrl: auth.configUrl,
|
|
90
|
+
audience: auth.audience,
|
|
91
|
+
tokenUrl: '',
|
|
92
|
+
grantType: '',
|
|
93
|
+
scope: auth.scope,
|
|
94
|
+
clientId: auth.clientId,
|
|
95
|
+
clientSecret: auth.clientSecret,
|
|
96
|
+
adminClientId: auth.admin?.clientId || '',
|
|
97
|
+
adminClientSecret: auth.admin?.clientSecret || '',
|
|
98
|
+
};
|
|
99
|
+
default:
|
|
100
|
+
throw new Error(`Unknown auth type`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
export const toNetwork = (table, authTable) => {
|
|
104
|
+
if (!authTable) {
|
|
105
|
+
throw new Error(`Missing auth table for network: ${table.name}`);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
name: table.name,
|
|
109
|
+
chainId: table.chainId,
|
|
110
|
+
synchronizerId: table.synchronizerId,
|
|
111
|
+
description: table.description,
|
|
112
|
+
ledgerApi: {
|
|
113
|
+
baseUrl: table.ledgerApiBaseUrl,
|
|
114
|
+
adminGrpcUrl: table.ledgerApiAdminGrpcUrl,
|
|
115
|
+
},
|
|
116
|
+
auth: toAuth(authTable),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
export const fromNetwork = (network, userId) => {
|
|
120
|
+
return {
|
|
121
|
+
name: network.name,
|
|
122
|
+
chainId: network.chainId,
|
|
123
|
+
synchronizerId: network.synchronizerId,
|
|
124
|
+
description: network.description,
|
|
125
|
+
ledgerApiBaseUrl: network.ledgerApi.baseUrl,
|
|
126
|
+
ledgerApiAdminGrpcUrl: network.ledgerApi.adminGrpcUrl,
|
|
127
|
+
userId: userId,
|
|
128
|
+
identityProviderId: network.auth.identityProviderId,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
export const fromWallet = (wallet, userId) => {
|
|
132
|
+
return {
|
|
133
|
+
...wallet,
|
|
134
|
+
primary: wallet.primary ? 1 : 0,
|
|
135
|
+
userId: userId,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
export const toWallet = (table) => {
|
|
139
|
+
return {
|
|
140
|
+
...table,
|
|
141
|
+
primary: table.primary === 1,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
export const fromTransaction = (transaction, userId) => {
|
|
145
|
+
return {
|
|
146
|
+
...transaction,
|
|
147
|
+
payload: transaction.payload
|
|
148
|
+
? JSON.stringify(transaction.payload)
|
|
149
|
+
: undefined,
|
|
150
|
+
userId: userId,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
export const toTransaction = (table) => {
|
|
154
|
+
return {
|
|
155
|
+
...table,
|
|
156
|
+
status: table.status,
|
|
157
|
+
payload: table.payload ? JSON.parse(table.payload) : undefined,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Logger } from 'pino';
|
|
2
|
+
import { AuthContext, AuthAware } from '@canton-network/core-wallet-auth';
|
|
3
|
+
import { Store as BaseStore, Wallet, PartyId, Session, WalletFilter, Transaction, Network, StoreConfig } from '@canton-network/core-wallet-store';
|
|
4
|
+
import { Kysely } from 'kysely';
|
|
5
|
+
import { DB } from './schema.js';
|
|
6
|
+
export declare class StoreSql implements BaseStore, AuthAware<StoreSql> {
|
|
7
|
+
private db;
|
|
8
|
+
private logger;
|
|
9
|
+
authContext: AuthContext | undefined;
|
|
10
|
+
constructor(db: Kysely<DB>, logger: Logger, authContext?: AuthContext);
|
|
11
|
+
withAuthContext(context?: AuthContext): StoreSql;
|
|
12
|
+
private assertConnected;
|
|
13
|
+
getWallets(filter?: WalletFilter): Promise<Array<Wallet>>;
|
|
14
|
+
getPrimaryWallet(): Promise<Wallet | undefined>;
|
|
15
|
+
setPrimaryWallet(partyId: PartyId): Promise<void>;
|
|
16
|
+
addWallet(wallet: Wallet): Promise<void>;
|
|
17
|
+
getSession(): Promise<Session | undefined>;
|
|
18
|
+
setSession(session: Session): Promise<void>;
|
|
19
|
+
removeSession(): Promise<void>;
|
|
20
|
+
getNetwork(chainId: string): Promise<Network>;
|
|
21
|
+
getCurrentNetwork(): Promise<Network>;
|
|
22
|
+
listNetworks(): Promise<Array<Network>>;
|
|
23
|
+
updateNetwork(network: Network): Promise<void>;
|
|
24
|
+
addNetwork(network: Network): Promise<void>;
|
|
25
|
+
removeNetwork(chainId: string): Promise<void>;
|
|
26
|
+
setTransaction(transaction: Transaction): Promise<void>;
|
|
27
|
+
getTransaction(commandId: string): Promise<Transaction | undefined>;
|
|
28
|
+
}
|
|
29
|
+
export declare const connection: (config: StoreConfig) => Kysely<DB>;
|
|
30
|
+
//# sourceMappingURL=store-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-sql.d.ts","sourceRoot":"","sources":["../src/store-sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EACH,WAAW,EAEX,SAAS,EACZ,MAAM,kCAAkC,CAAA;AACzC,OAAO,EACH,KAAK,IAAI,SAAS,EAClB,MAAM,EACN,OAAO,EACP,OAAO,EACP,YAAY,EACZ,WAAW,EACX,OAAO,EACP,WAAW,EACd,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAmB,MAAM,EAAiB,MAAM,QAAQ,CAAA;AAE/D,OAAO,EACH,EAAE,EAQL,MAAM,aAAa,CAAA;AAEpB,qBAAa,QAAS,YAAW,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC;IAIvD,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,MAAM;IAJlB,WAAW,EAAE,WAAW,GAAG,SAAS,CAAA;gBAGxB,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,MAAM,EAAE,MAAM,EACtB,WAAW,CAAC,EAAE,WAAW;IAQ7B,eAAe,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,QAAQ;IAIhD,OAAO,CAAC,eAAe;IASjB,UAAU,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IA2B7D,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK/C,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBjD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCxC,UAAU,IAAI,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAQ1C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW7C,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBrC,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAuBvC,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB3C,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B7C,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;CAc5E;AAED,eAAO,MAAM,UAAU,GAAI,QAAQ,WAAW,eAqB7C,CAAA"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { fromAuth, fromNetwork, fromTransaction, fromWallet, toNetwork, toTransaction, toWallet, } from './schema.js';
|
|
4
|
+
export class StoreSql {
|
|
5
|
+
db;
|
|
6
|
+
logger;
|
|
7
|
+
authContext;
|
|
8
|
+
constructor(db, logger, authContext) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.logger = logger.child({ component: 'StoreInternal' });
|
|
12
|
+
this.authContext = authContext;
|
|
13
|
+
// this.syncWallets()
|
|
14
|
+
}
|
|
15
|
+
withAuthContext(context) {
|
|
16
|
+
return new StoreSql(this.db, this.logger, context);
|
|
17
|
+
}
|
|
18
|
+
assertConnected() {
|
|
19
|
+
if (!this.authContext) {
|
|
20
|
+
throw new Error('User is not connected');
|
|
21
|
+
}
|
|
22
|
+
return this.authContext.userId;
|
|
23
|
+
}
|
|
24
|
+
// Wallet methods
|
|
25
|
+
async getWallets(filter = {}) {
|
|
26
|
+
const userId = this.assertConnected();
|
|
27
|
+
const { chainIds, signingProviderIds } = filter;
|
|
28
|
+
const chainIdSet = chainIds ? new Set(chainIds) : null;
|
|
29
|
+
const signingProviderIdSet = signingProviderIds
|
|
30
|
+
? new Set(signingProviderIds)
|
|
31
|
+
: null;
|
|
32
|
+
const wallets = await this.db
|
|
33
|
+
.selectFrom('wallets')
|
|
34
|
+
.selectAll()
|
|
35
|
+
.where('userId', '=', userId)
|
|
36
|
+
.execute();
|
|
37
|
+
return wallets
|
|
38
|
+
.filter((wallet) => {
|
|
39
|
+
const matchedChainIds = chainIdSet
|
|
40
|
+
? chainIdSet.has(wallet.chainId)
|
|
41
|
+
: true;
|
|
42
|
+
const matchedStorageProviderIdS = signingProviderIdSet
|
|
43
|
+
? signingProviderIdSet.has(wallet.signingProviderId)
|
|
44
|
+
: true;
|
|
45
|
+
return matchedChainIds && matchedStorageProviderIdS;
|
|
46
|
+
})
|
|
47
|
+
.map((table) => toWallet(table));
|
|
48
|
+
}
|
|
49
|
+
async getPrimaryWallet() {
|
|
50
|
+
const wallets = await this.getWallets();
|
|
51
|
+
return wallets.find((w) => w.primary === true);
|
|
52
|
+
}
|
|
53
|
+
async setPrimaryWallet(partyId) {
|
|
54
|
+
const wallets = await this.getWallets();
|
|
55
|
+
if (!wallets.some((w) => w.partyId === partyId)) {
|
|
56
|
+
throw new Error(`Wallet with partyId "${partyId}" not found`);
|
|
57
|
+
}
|
|
58
|
+
const primary = wallets.find((w) => w.primary === true);
|
|
59
|
+
await this.db.transaction().execute(async (trx) => {
|
|
60
|
+
if (primary) {
|
|
61
|
+
await trx
|
|
62
|
+
.updateTable('wallets')
|
|
63
|
+
.set({ primary: 0 })
|
|
64
|
+
.where('partyId', '=', primary.partyId)
|
|
65
|
+
.execute();
|
|
66
|
+
}
|
|
67
|
+
await trx
|
|
68
|
+
.updateTable('wallets')
|
|
69
|
+
.set({ primary: 1 })
|
|
70
|
+
.where('partyId', '=', partyId)
|
|
71
|
+
.execute();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async addWallet(wallet) {
|
|
75
|
+
const userId = this.assertConnected();
|
|
76
|
+
const wallets = await this.getWallets();
|
|
77
|
+
if (wallets.some((w) => w.partyId === wallet.partyId)) {
|
|
78
|
+
throw new Error(`Wallet with partyId "${wallet.partyId}" already exists`);
|
|
79
|
+
}
|
|
80
|
+
if (wallets.length === 0) {
|
|
81
|
+
// If this is the first wallet, set it as primary automatically
|
|
82
|
+
wallet.primary = true;
|
|
83
|
+
}
|
|
84
|
+
await this.db.transaction().execute(async (trx) => {
|
|
85
|
+
if (wallet.primary) {
|
|
86
|
+
// If the new wallet is primary, set all others to non-primary
|
|
87
|
+
await trx
|
|
88
|
+
.updateTable('wallets')
|
|
89
|
+
.set({ primary: 0 })
|
|
90
|
+
.where((eb) => eb.and([
|
|
91
|
+
eb('primary', '=', 1),
|
|
92
|
+
eb('userId', '=', userId),
|
|
93
|
+
]))
|
|
94
|
+
.execute();
|
|
95
|
+
}
|
|
96
|
+
await trx
|
|
97
|
+
.insertInto('wallets')
|
|
98
|
+
.values(fromWallet(wallet, userId))
|
|
99
|
+
.execute();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Session methods
|
|
103
|
+
async getSession() {
|
|
104
|
+
const sessions = await this.db
|
|
105
|
+
.selectFrom('sessions')
|
|
106
|
+
.selectAll()
|
|
107
|
+
.executeTakeFirst();
|
|
108
|
+
return sessions;
|
|
109
|
+
}
|
|
110
|
+
async setSession(session) {
|
|
111
|
+
const userId = this.assertConnected();
|
|
112
|
+
await this.db.transaction().execute(async (trx) => {
|
|
113
|
+
await trx
|
|
114
|
+
.deleteFrom('sessions')
|
|
115
|
+
.where('userId', '=', userId)
|
|
116
|
+
.execute();
|
|
117
|
+
await trx
|
|
118
|
+
.insertInto('sessions')
|
|
119
|
+
.values({ ...session, userId })
|
|
120
|
+
.execute();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async removeSession() {
|
|
124
|
+
const userId = this.assertConnected();
|
|
125
|
+
await this.db
|
|
126
|
+
.deleteFrom('sessions')
|
|
127
|
+
.where('userId', '=', userId)
|
|
128
|
+
.execute();
|
|
129
|
+
}
|
|
130
|
+
// Network methods
|
|
131
|
+
async getNetwork(chainId) {
|
|
132
|
+
this.assertConnected();
|
|
133
|
+
const networks = await this.listNetworks();
|
|
134
|
+
if (!networks)
|
|
135
|
+
throw new Error('No networks available');
|
|
136
|
+
const network = networks.find((n) => n.chainId === chainId);
|
|
137
|
+
if (!network)
|
|
138
|
+
throw new Error(`Network "${chainId}" not found`);
|
|
139
|
+
return network;
|
|
140
|
+
}
|
|
141
|
+
async getCurrentNetwork() {
|
|
142
|
+
const session = await this.getSession();
|
|
143
|
+
if (!session) {
|
|
144
|
+
throw new Error('No session found');
|
|
145
|
+
}
|
|
146
|
+
const chainId = session.network;
|
|
147
|
+
if (!chainId) {
|
|
148
|
+
throw new Error('No current network set in session');
|
|
149
|
+
}
|
|
150
|
+
const networks = await this.listNetworks();
|
|
151
|
+
const network = networks.find((n) => n.chainId === chainId);
|
|
152
|
+
if (!network) {
|
|
153
|
+
throw new Error(`Network "${chainId}" not found`);
|
|
154
|
+
}
|
|
155
|
+
return network;
|
|
156
|
+
}
|
|
157
|
+
async listNetworks() {
|
|
158
|
+
let query = this.db.selectFrom('networks').selectAll();
|
|
159
|
+
if (this.authContext) {
|
|
160
|
+
const userId = this.assertConnected();
|
|
161
|
+
query = query.where((eb) => eb.or([
|
|
162
|
+
eb('userId', 'is', null), // Global networks
|
|
163
|
+
eb('userId', '=', userId), // User-specific networks
|
|
164
|
+
]));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
query = query.where('userId', 'is', null); // Only global networks
|
|
168
|
+
}
|
|
169
|
+
const networks = await query.execute();
|
|
170
|
+
const idps = await this.db.selectFrom('idps').selectAll().execute();
|
|
171
|
+
const idpMap = new Map(idps.map((idp) => [idp.identityProviderId, idp]));
|
|
172
|
+
return networks.map((table) => toNetwork(table, idpMap.get(table.identityProviderId)));
|
|
173
|
+
}
|
|
174
|
+
async updateNetwork(network) {
|
|
175
|
+
const userId = this.assertConnected();
|
|
176
|
+
// todo: check and compare userid of existing network
|
|
177
|
+
await this.db.transaction().execute(async (trx) => {
|
|
178
|
+
await trx
|
|
179
|
+
.updateTable('networks')
|
|
180
|
+
.set(fromNetwork(network, userId))
|
|
181
|
+
.where('chainId', '=', network.chainId)
|
|
182
|
+
.execute();
|
|
183
|
+
await trx
|
|
184
|
+
.updateTable('idps')
|
|
185
|
+
.set(fromAuth(network.auth))
|
|
186
|
+
.where('identityProviderId', '=', network.auth.identityProviderId)
|
|
187
|
+
.execute();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
async addNetwork(network) {
|
|
191
|
+
const userId = this.authContext?.userId;
|
|
192
|
+
await this.db.transaction().execute(async (trx) => {
|
|
193
|
+
const networkAlreadyExists = await trx
|
|
194
|
+
.selectFrom('networks')
|
|
195
|
+
.selectAll()
|
|
196
|
+
.where('chainId', '=', network.chainId)
|
|
197
|
+
.executeTakeFirst();
|
|
198
|
+
if (networkAlreadyExists) {
|
|
199
|
+
throw new Error(`Network ${network.chainId} already exists`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
await trx
|
|
203
|
+
.insertInto('idps')
|
|
204
|
+
.values(fromAuth(network.auth))
|
|
205
|
+
.execute();
|
|
206
|
+
await trx
|
|
207
|
+
.insertInto('networks')
|
|
208
|
+
.values(fromNetwork(network, userId))
|
|
209
|
+
.execute();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async removeNetwork(chainId) {
|
|
214
|
+
const userId = this.assertConnected();
|
|
215
|
+
await this.db.transaction().execute(async (trx) => {
|
|
216
|
+
const network = await trx
|
|
217
|
+
.selectFrom('networks')
|
|
218
|
+
.selectAll()
|
|
219
|
+
.where('chainId', '=', chainId)
|
|
220
|
+
.executeTakeFirst();
|
|
221
|
+
if (!network) {
|
|
222
|
+
throw new Error(`Network ${chainId} does not exists`);
|
|
223
|
+
}
|
|
224
|
+
if (network.userId !== userId) {
|
|
225
|
+
throw new Error(`Network ${chainId} is not owned by user ${userId}`);
|
|
226
|
+
}
|
|
227
|
+
await trx
|
|
228
|
+
.deleteFrom('networks')
|
|
229
|
+
.where('chainId', '=', chainId)
|
|
230
|
+
.execute();
|
|
231
|
+
await trx
|
|
232
|
+
.deleteFrom('idps')
|
|
233
|
+
.where('identityProviderId', '=', network.identityProviderId)
|
|
234
|
+
.execute();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// Transaction methods
|
|
238
|
+
async setTransaction(transaction) {
|
|
239
|
+
const userId = this.assertConnected();
|
|
240
|
+
const existing = await this.getTransaction(transaction.commandId);
|
|
241
|
+
if (existing) {
|
|
242
|
+
await this.db
|
|
243
|
+
.updateTable('transactions')
|
|
244
|
+
.set(fromTransaction(transaction, userId))
|
|
245
|
+
.where('commandId', '=', transaction.commandId)
|
|
246
|
+
.execute();
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
await this.db
|
|
250
|
+
.insertInto('transactions')
|
|
251
|
+
.values(fromTransaction(transaction, userId))
|
|
252
|
+
.execute();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async getTransaction(commandId) {
|
|
256
|
+
const userId = this.assertConnected();
|
|
257
|
+
const transaction = await this.db
|
|
258
|
+
.selectFrom('transactions')
|
|
259
|
+
.selectAll()
|
|
260
|
+
.where((eb) => eb.and([
|
|
261
|
+
eb('commandId', '=', commandId),
|
|
262
|
+
eb('userId', '=', userId),
|
|
263
|
+
]))
|
|
264
|
+
.executeTakeFirst();
|
|
265
|
+
return transaction ? toTransaction(transaction) : undefined;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export const connection = (config) => {
|
|
269
|
+
switch (config.connection.type) {
|
|
270
|
+
case 'sqlite':
|
|
271
|
+
return new Kysely({
|
|
272
|
+
dialect: new SqliteDialect({
|
|
273
|
+
database: new Database(config.connection.database),
|
|
274
|
+
}),
|
|
275
|
+
plugins: [new CamelCasePlugin()],
|
|
276
|
+
});
|
|
277
|
+
case 'memory':
|
|
278
|
+
return new Kysely({
|
|
279
|
+
dialect: new SqliteDialect({
|
|
280
|
+
database: new Database(':memory:'),
|
|
281
|
+
}),
|
|
282
|
+
plugins: [new CamelCasePlugin()],
|
|
283
|
+
});
|
|
284
|
+
default:
|
|
285
|
+
throw new Error(`Unsupported database type: ${config.connection.type}`);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-sql.test.d.ts","sourceRoot":"","sources":["../src/store-sql.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { pino } from 'pino';
|
|
3
|
+
import { sink } from 'pino-test';
|
|
4
|
+
import { migrator } from './migrator';
|
|
5
|
+
import { connection, StoreSql } from './store-sql';
|
|
6
|
+
const authContextMock = {
|
|
7
|
+
userId: 'test-user-id',
|
|
8
|
+
accessToken: 'test-access-token',
|
|
9
|
+
};
|
|
10
|
+
const storeConfig = {
|
|
11
|
+
connection: {
|
|
12
|
+
type: 'memory',
|
|
13
|
+
},
|
|
14
|
+
networks: [],
|
|
15
|
+
};
|
|
16
|
+
const implementations = [['StoreSql', StoreSql]];
|
|
17
|
+
const ledgerApi = {
|
|
18
|
+
baseUrl: 'http://api',
|
|
19
|
+
adminGrpcUrl: 'http://grpc',
|
|
20
|
+
};
|
|
21
|
+
const auth = {
|
|
22
|
+
identityProviderId: 'idp1',
|
|
23
|
+
type: 'password',
|
|
24
|
+
issuer: 'http://auth',
|
|
25
|
+
configUrl: 'http://auth/.well-known/openid-configuration',
|
|
26
|
+
tokenUrl: 'http://auth',
|
|
27
|
+
grantType: 'password',
|
|
28
|
+
clientId: 'cid',
|
|
29
|
+
scope: 'scope',
|
|
30
|
+
audience: 'aud',
|
|
31
|
+
};
|
|
32
|
+
const network = {
|
|
33
|
+
name: 'testnet',
|
|
34
|
+
chainId: 'network1',
|
|
35
|
+
synchronizerId: 'sync1::fingerprint',
|
|
36
|
+
description: 'Test Network',
|
|
37
|
+
ledgerApi,
|
|
38
|
+
auth,
|
|
39
|
+
};
|
|
40
|
+
implementations.forEach(([name, StoreImpl]) => {
|
|
41
|
+
describe(name, () => {
|
|
42
|
+
let db;
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
db = connection(storeConfig);
|
|
45
|
+
const umzug = migrator(db);
|
|
46
|
+
await umzug.up();
|
|
47
|
+
});
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
await db.destroy();
|
|
50
|
+
});
|
|
51
|
+
test('should add and retrieve wallets', async () => {
|
|
52
|
+
const wallet = {
|
|
53
|
+
primary: false,
|
|
54
|
+
partyId: 'party1',
|
|
55
|
+
hint: 'hint',
|
|
56
|
+
signingProviderId: 'internal',
|
|
57
|
+
publicKey: 'publicKey',
|
|
58
|
+
namespace: 'namespace',
|
|
59
|
+
chainId: 'network1',
|
|
60
|
+
};
|
|
61
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
62
|
+
await store.addNetwork(network);
|
|
63
|
+
await store.addWallet(wallet);
|
|
64
|
+
const wallets = await store.getWallets();
|
|
65
|
+
expect(wallets).toHaveLength(1);
|
|
66
|
+
});
|
|
67
|
+
test('should filter wallets', async () => {
|
|
68
|
+
const auth2 = {
|
|
69
|
+
identityProviderId: 'idp2',
|
|
70
|
+
type: 'password',
|
|
71
|
+
issuer: 'http://auth',
|
|
72
|
+
configUrl: 'http://auth/.well-known/openid-configuration',
|
|
73
|
+
tokenUrl: 'http://auth',
|
|
74
|
+
grantType: 'password',
|
|
75
|
+
clientId: 'cid',
|
|
76
|
+
scope: 'scope',
|
|
77
|
+
audience: 'aud',
|
|
78
|
+
};
|
|
79
|
+
const network2 = {
|
|
80
|
+
name: 'testnet',
|
|
81
|
+
chainId: 'network2',
|
|
82
|
+
synchronizerId: 'sync1::fingerprint',
|
|
83
|
+
description: 'Test Network',
|
|
84
|
+
ledgerApi,
|
|
85
|
+
auth: auth2,
|
|
86
|
+
};
|
|
87
|
+
const wallet1 = {
|
|
88
|
+
primary: false,
|
|
89
|
+
partyId: 'party1',
|
|
90
|
+
hint: 'hint1',
|
|
91
|
+
signingProviderId: 'internal',
|
|
92
|
+
publicKey: 'publicKey',
|
|
93
|
+
namespace: 'namespace',
|
|
94
|
+
chainId: 'network1',
|
|
95
|
+
};
|
|
96
|
+
const wallet2 = {
|
|
97
|
+
primary: false,
|
|
98
|
+
partyId: 'party2',
|
|
99
|
+
hint: 'hint2',
|
|
100
|
+
signingProviderId: 'internal',
|
|
101
|
+
publicKey: 'publicKey',
|
|
102
|
+
namespace: 'namespace',
|
|
103
|
+
chainId: 'network1',
|
|
104
|
+
};
|
|
105
|
+
const wallet3 = {
|
|
106
|
+
primary: false,
|
|
107
|
+
partyId: 'party3',
|
|
108
|
+
hint: 'hint3',
|
|
109
|
+
signingProviderId: 'internal',
|
|
110
|
+
publicKey: 'publicKey',
|
|
111
|
+
namespace: 'namespace',
|
|
112
|
+
chainId: 'network2',
|
|
113
|
+
};
|
|
114
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
115
|
+
await store.addNetwork(network);
|
|
116
|
+
await store.addNetwork(network2);
|
|
117
|
+
await store.addWallet(wallet1);
|
|
118
|
+
await store.addWallet(wallet2);
|
|
119
|
+
await store.addWallet(wallet3);
|
|
120
|
+
const getAllWallets = await store.getWallets();
|
|
121
|
+
const getWalletsByChainId = await store.getWallets({
|
|
122
|
+
chainIds: ['network1'],
|
|
123
|
+
});
|
|
124
|
+
const getWalletsBySigningProviderId = await store.getWallets({
|
|
125
|
+
signingProviderIds: ['internal'],
|
|
126
|
+
});
|
|
127
|
+
const getWalletsByChainIdAndSigningProviderId = await store.getWallets({
|
|
128
|
+
chainIds: ['network1'],
|
|
129
|
+
signingProviderIds: ['internal'],
|
|
130
|
+
});
|
|
131
|
+
expect(getAllWallets).toHaveLength(3);
|
|
132
|
+
expect(getWalletsByChainId).toHaveLength(2);
|
|
133
|
+
expect(getWalletsBySigningProviderId).toHaveLength(3);
|
|
134
|
+
expect(getWalletsByChainIdAndSigningProviderId).toHaveLength(2);
|
|
135
|
+
});
|
|
136
|
+
test('should set and get primary wallet', async () => {
|
|
137
|
+
const wallet1 = {
|
|
138
|
+
primary: false,
|
|
139
|
+
partyId: 'party1',
|
|
140
|
+
hint: 'hint1',
|
|
141
|
+
signingProviderId: 'internal',
|
|
142
|
+
publicKey: 'publicKey',
|
|
143
|
+
namespace: 'namespace',
|
|
144
|
+
chainId: 'network1',
|
|
145
|
+
};
|
|
146
|
+
const wallet2 = {
|
|
147
|
+
primary: false,
|
|
148
|
+
partyId: 'party2',
|
|
149
|
+
hint: 'hint2',
|
|
150
|
+
signingProviderId: 'internal',
|
|
151
|
+
publicKey: 'publicKey',
|
|
152
|
+
namespace: 'namespace',
|
|
153
|
+
chainId: 'network1',
|
|
154
|
+
};
|
|
155
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
156
|
+
await store.addNetwork(network);
|
|
157
|
+
await store.addWallet(wallet1);
|
|
158
|
+
await store.addWallet(wallet2);
|
|
159
|
+
await store.setPrimaryWallet('party2');
|
|
160
|
+
const primary = await store.getPrimaryWallet();
|
|
161
|
+
expect(primary?.partyId).toBe('party2');
|
|
162
|
+
expect(primary?.primary).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
test('should set and get session', async () => {
|
|
165
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
166
|
+
await store.addNetwork(network);
|
|
167
|
+
const session = {
|
|
168
|
+
network: 'network1',
|
|
169
|
+
accessToken: 'token',
|
|
170
|
+
};
|
|
171
|
+
await store.setSession(session);
|
|
172
|
+
const result = await store.getSession();
|
|
173
|
+
expect(result).toEqual({
|
|
174
|
+
...session,
|
|
175
|
+
userId: authContextMock.userId,
|
|
176
|
+
});
|
|
177
|
+
await store.removeSession();
|
|
178
|
+
const removed = await store.getSession();
|
|
179
|
+
expect(removed).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
test('should add, list, get, update, and remove networks', async () => {
|
|
182
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
183
|
+
await store.addNetwork(network);
|
|
184
|
+
const listed = await store.listNetworks();
|
|
185
|
+
expect(listed).toHaveLength(1);
|
|
186
|
+
expect(listed[0].description).toBe('Test Network');
|
|
187
|
+
await store.updateNetwork({
|
|
188
|
+
...network,
|
|
189
|
+
description: 'Updated Network',
|
|
190
|
+
});
|
|
191
|
+
const fetched = await store.getNetwork('network1');
|
|
192
|
+
expect(fetched.description).toBe('Updated Network');
|
|
193
|
+
await store.removeNetwork('network1');
|
|
194
|
+
const afterRemove = await store.listNetworks();
|
|
195
|
+
expect(afterRemove).toHaveLength(0);
|
|
196
|
+
});
|
|
197
|
+
test('should throw when getting a non-existent network', async () => {
|
|
198
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
199
|
+
await expect(store.getNetwork('doesnotexist')).rejects.toThrow();
|
|
200
|
+
});
|
|
201
|
+
test('should throw when getting current network if none set', async () => {
|
|
202
|
+
const store = new StoreImpl(db, pino(sink()), authContextMock);
|
|
203
|
+
await expect(store.getCurrentNetwork()).rejects.toThrow();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@canton-network/core-wallet-store-sql",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"repository": "https://github.com/hyperledger-labs/splice-wallet-kernel",
|
|
6
|
+
"description": "SQL implementation of the Store API",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"author": "Marc Juchli <marc.juchli@digitalasset.com>",
|
|
9
|
+
"packageManager": "yarn@4.9.2",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -b",
|
|
14
|
+
"dev": "tsc -b --watch",
|
|
15
|
+
"clean": "tsc -b --clean; rm -rf dist",
|
|
16
|
+
"test": "yarn node --experimental-vm-modules $(yarn bin jest)"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@canton-network/core-ledger-client": "^0.1.0",
|
|
20
|
+
"@canton-network/core-wallet-auth": "^0.1.0",
|
|
21
|
+
"@canton-network/core-wallet-store": "^0.1.0",
|
|
22
|
+
"better-sqlite3": "^12.2.0",
|
|
23
|
+
"commander": "^14.0.0",
|
|
24
|
+
"kysely": "^0.28.5",
|
|
25
|
+
"pino": "^9.7.0",
|
|
26
|
+
"umzug": "^3.8.2",
|
|
27
|
+
"zod": "^3.25.64"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@jest/globals": "^29.0.0",
|
|
31
|
+
"@swc/core": "^1.11.31",
|
|
32
|
+
"@swc/jest": "^0.2.38",
|
|
33
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
34
|
+
"@types/jest": "^30.0.0",
|
|
35
|
+
"jest": "^30.0.0",
|
|
36
|
+
"pino-test": "^1.1.0",
|
|
37
|
+
"ts-jest": "^29.4.0",
|
|
38
|
+
"ts-jest-resolver": "^2.0.1",
|
|
39
|
+
"tsx": "^4.20.4",
|
|
40
|
+
"typescript": "^5.8.3"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist/*"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|