@fivenorth/loop-sdk 0.1.1 → 0.1.2

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 (3) hide show
  1. package/README.md +135 -3
  2. package/dist/index.js +23 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,15 +2,147 @@
2
2
 
3
3
  Loop SDK allows dApps connect to a [Loop](https://cantonloop.com) account. The Loop wallet can be on mobile or on a desktop browser. All the interaction will happen inside the dApp. For signing, user will be prompted to sign either on their Loop wallet on mobile devices or on browser.
4
4
 
5
- # Limitation
5
+ ## Limitation
6
6
 
7
7
  Currently, we only support DAML transaction from the Splice build-in DAR files and Utility app DAR files.
8
8
 
9
9
  There is no plan to upload and support third party DAR at this moment
10
10
 
11
- # Usage guide
11
+ ## Quick overview
12
12
 
13
- Refer to the file `demo/test.html` to see an example of setup and use the SDK.
13
+ For a quick overview of how the code look like, you can take a look at this pen https://codepen.io/kureikain/pen/KwVGgLX.
14
+
15
+ ## Usage guide
16
+
17
+ To use the Loop SDK, you first need to install it from NPM:
18
+
19
+ ```bash
20
+ bun add @fivenorth/loop-sdk
21
+ ```
22
+
23
+ Then you can import it in your dApp:
24
+
25
+ ```javascript
26
+ import { loop } from '@fivenorth/loop-sdk';
27
+ ```
28
+
29
+ Note that, If you don't want to implement a build process, you can include the file directly with `unpkg` such as
30
+
31
+ ```javascript
32
+ import { loop } from 'https://unpkg.com/@fivenorth/loop-sdk@0.1.1/dist';
33
+ ```
34
+
35
+ An example of how we use it in that manner is on our [loopsdk demo](https://codepen.io/kureikain/pen/KwVGgLX)
36
+
37
+ ### 1. Initialize the SDK
38
+
39
+ Before you can connect, you need to initialize the SDK. This is typically done once when your application loads.
40
+
41
+ ```javascript
42
+ loop.init({
43
+ appName: 'My Awesome dApp',
44
+ network: 'local', // or 'devnet', 'mainnet'
45
+ onAccept: (provider) => {
46
+ console.log('Connected!', provider);
47
+ // You can now use the provider to interact with the wallet
48
+ },
49
+ onReject: () => {
50
+ console.log('Connection rejected by user.');
51
+ },
52
+ });
53
+ ```
54
+
55
+ The `init` method takes a configuration object with the following properties:
56
+ - `appName`: The name of your application, which will be displayed to the user in the Loop wallet.
57
+ - `network`: The network to connect to. Can be `local`, `devnet`, or `mainnet`.
58
+ - `onAccept`: A callback function that is called when the user accepts the connection. It receives a `provider` object.
59
+ - `onReject`: A callback function that is called when the user rejects the connection.
60
+
61
+ ### 2. Connect to the wallet
62
+
63
+ To initiate the connection, call `loop.connect()`:
64
+
65
+ ```javascript
66
+ loop.connect();
67
+ ```
68
+
69
+ This will open a modal with a QR code for the user to scan with their Loop wallet.
70
+
71
+ ### 3. Using the Provider
72
+
73
+ Once the connection is established, the `onAccept` callback will receive a `provider` object. This object provides methods to interact with the user's wallet and the DAML ledger.
74
+
75
+ The provider object has the `party_id` of the connected user.
76
+
77
+ #### Get Holdings
78
+
79
+ To get the user's token holdings:
80
+
81
+ ```javascript
82
+ const holdings = await provider.getHolding();
83
+ console.log(holdings);
84
+ ```
85
+
86
+ #### Get Active Contracts
87
+
88
+ You can query for active contracts by `templateId` or `interfaceId`.
89
+
90
+ By Template ID:
91
+ ```javascript
92
+ const contracts = await provider.getActiveContracts({
93
+ templateId: '#splice-amulet:Splice.Amulet:Amulet'
94
+ });
95
+ console.log(contracts);
96
+ ```
97
+
98
+ By Interface ID:
99
+ ```javascript
100
+ const contracts = await provider.getActiveContracts({
101
+ interfaceId: '#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding'
102
+ });
103
+ console.log(contracts);
104
+ ```
105
+
106
+ #### Submit a Transaction
107
+
108
+ To submit a DAML transaction, you need to construct a command object and pass it to `submitTransaction`:
109
+
110
+ ```javascript
111
+ const damlCommand = {
112
+ commands: [{
113
+ ExerciseCommand: {
114
+ templateId: "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory",
115
+ contractId: 'your-contract-id', // The contract ID to exercise the choice on
116
+ choice: 'TransferFactory_Transfer',
117
+ choiceArgument: {
118
+ // ... your choice arguments
119
+ }
120
+ }
121
+ }],
122
+ // ... other command properties
123
+ };
124
+
125
+ try {
126
+ const result = await provider.submitTransaction(damlCommand);
127
+ console.log('Transaction successful:', result);
128
+ } catch (error) {
129
+ console.error('Transaction failed:', error);
130
+ }
131
+ ```
132
+
133
+ #### Sign a Message
134
+
135
+ You can request the user to sign an arbitrary message:
136
+
137
+ ```javascript
138
+ const message = 'Hello, Loop!';
139
+ try {
140
+ const signature = await provider.signMessage(message);
141
+ console.log('Signature:', signature);
142
+ } catch (error) {
143
+ console.error('Signing failed:', error);
144
+ }
145
+ ```
14
146
 
15
147
  # API
16
148
 
package/dist/index.js CHANGED
@@ -2164,7 +2164,16 @@ class Connection {
2164
2164
  if (!response.ok) {
2165
2165
  throw new Error("Session verification failed.");
2166
2166
  }
2167
- return response.json();
2167
+ const data = await response.json();
2168
+ if (!data?.party_id || !data?.public_key) {
2169
+ throw new Error("Invalid session verification response.");
2170
+ }
2171
+ const account = {
2172
+ party_id: data?.party_id,
2173
+ auth_token: authToken,
2174
+ public_key: data?.public_key
2175
+ };
2176
+ return account;
2168
2177
  }
2169
2178
  websocketUrl(ticketId) {
2170
2179
  return `${this.network === "local" ? "ws" : "wss"}://${this.apiUrl.replace("https://", "").replace("http://", "")}/api/v1/.connect/pair/ws/${ticketId}`;
@@ -2203,15 +2212,17 @@ function generateRequestId() {
2203
2212
  class Provider {
2204
2213
  connection;
2205
2214
  party_id;
2215
+ public_key;
2206
2216
  auth_token;
2207
2217
  requests = new Map;
2208
2218
  requestTimeout = 30000;
2209
- constructor({ connection, party_id, auth_token, requestTimeout }) {
2219
+ constructor({ connection, party_id, public_key, auth_token, requestTimeout }) {
2210
2220
  if (!connection) {
2211
2221
  throw new Error("Provider requires a connection object.");
2212
2222
  }
2213
2223
  this.connection = connection;
2214
2224
  this.party_id = party_id;
2225
+ this.public_key = public_key;
2215
2226
  this.auth_token = auth_token;
2216
2227
  this.requestTimeout = requestTimeout || 30000;
2217
2228
  }
@@ -2297,21 +2308,20 @@ class LoopSDK {
2297
2308
  const existingConnectionRaw = localStorage.getItem("loop_connect");
2298
2309
  if (existingConnectionRaw) {
2299
2310
  try {
2300
- console.log("Existing connection raw:", existingConnectionRaw);
2301
- const { ticketId, authToken, partyId } = JSON.parse(existingConnectionRaw);
2302
- if (authToken && partyId) {
2311
+ const { ticketId, authToken, partyId, publicKey } = JSON.parse(existingConnectionRaw);
2312
+ if (authToken && partyId && publicKey) {
2303
2313
  try {
2304
2314
  const verifiedAccount = await this.connection.verifySession(authToken);
2305
2315
  if (verifiedAccount.party_id === partyId) {
2306
- this.provider = new Provider({ connection: this.connection, party_id: partyId, auth_token: authToken });
2316
+ this.provider = new Provider({ connection: this.connection, party_id: partyId, auth_token: authToken, public_key: publicKey });
2307
2317
  this.onAccept?.(this.provider);
2308
2318
  if (ticketId) {
2309
2319
  this.connection.connectWebSocket(ticketId, this.handleWebSocketMessage.bind(this));
2310
2320
  }
2311
2321
  return;
2312
2322
  }
2313
- } catch (error) {
2314
- console.log("Auto-login failed, token is invalid. Starting new connection.");
2323
+ } catch (err) {
2324
+ console.error("Auto-login failed, token is invalid. Starting new connection.", err);
2315
2325
  }
2316
2326
  }
2317
2327
  if (ticketId) {
@@ -2326,7 +2336,7 @@ class LoopSDK {
2326
2336
  }
2327
2337
  localStorage.removeItem("loop_connect");
2328
2338
  }
2329
- const sessionId = this.generateSessionId();
2339
+ const sessionId = generateRequestId();
2330
2340
  try {
2331
2341
  const { ticket_id: ticketId } = await this.connection.getTicket(this.appName, sessionId, this.version);
2332
2342
  this.ticketId = ticketId;
@@ -2340,31 +2350,27 @@ class LoopSDK {
2340
2350
  }
2341
2351
  }
2342
2352
  handleWebSocketMessage(event) {
2343
- console.log("Received message:", event.data);
2344
2353
  const message = JSON.parse(event.data);
2345
2354
  if (message.type === "handshake_accept" /* HANDSHAKE_ACCEPT */) {
2346
- const { authToken, partyId } = message.payload || {};
2347
- console.log("Handshake accepted, token and account received.", authToken, partyId);
2348
- if (authToken && partyId) {
2349
- this.provider = new Provider({ connection: this.connection, party_id: partyId, auth_token: authToken });
2350
- console.log("Handshake accepted, token and account received.");
2355
+ const { authToken, partyId, publicKey } = message.payload || {};
2356
+ if (authToken && partyId && publicKey) {
2357
+ this.provider = new Provider({ connection: this.connection, party_id: partyId, auth_token: authToken, public_key: publicKey });
2351
2358
  const connectionInfoRaw = localStorage.getItem("loop_connect");
2352
2359
  if (connectionInfoRaw) {
2353
2360
  try {
2354
2361
  const connectionInfo = JSON.parse(connectionInfoRaw);
2355
2362
  connectionInfo.authToken = authToken;
2356
2363
  connectionInfo.partyId = partyId;
2364
+ connectionInfo.publicKey = publicKey;
2357
2365
  localStorage.setItem("loop_connect", JSON.stringify(connectionInfo));
2358
2366
  this.onAccept?.(this.provider);
2359
2367
  this.hideQrCode();
2360
- this.connection?.connectWebSocket(connectionInfo.ticketId, this.handleWebSocketMessage.bind(this));
2361
2368
  } catch (error) {
2362
2369
  console.error("Failed to update local storage with auth token.", error);
2363
2370
  }
2364
2371
  }
2365
2372
  }
2366
2373
  } else if (message.type === "handshake_reject" /* HANDSHAKE_REJECT */) {
2367
- console.log("Connection rejected.");
2368
2374
  localStorage.removeItem("loop_connect");
2369
2375
  this.connection?.ws?.close();
2370
2376
  this.onReject?.();
@@ -2419,9 +2425,6 @@ class LoopSDK {
2419
2425
  this.overlay = null;
2420
2426
  }
2421
2427
  }
2422
- generateSessionId() {
2423
- return crypto.randomUUID();
2424
- }
2425
2428
  }
2426
2429
  var loop = new LoopSDK;
2427
2430
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fivenorth/loop-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "author": "hello@fivenorth.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",