@fivenorth/loop-sdk 0.1.0 → 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 +139 -5
  2. package/dist/index.js +26 -20
  3. package/package.json +14 -14
package/README.md CHANGED
@@ -1,20 +1,153 @@
1
- # LOOP SDK
1
+ # Loop SDK
2
2
 
3
- Loop SDK allow dApps connect to a Loop 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.
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
 
17
149
 
150
+
18
151
  # Development Guide
19
152
 
20
153
  This section is only if you want to actively develop the SDK itself. To use the SDK, follow the `#Usage Guide` section
@@ -36,4 +169,5 @@ bun run index.ts
36
169
 
37
170
  ```
38
171
  bun run build
172
+ bun publish
39
173
  ```
package/dist/index.js CHANGED
@@ -2080,14 +2080,17 @@ class Connection {
2080
2080
  this.walletUrl = "http://localhost:3000";
2081
2081
  this.apiUrl = "http://localhost:8080";
2082
2082
  break;
2083
+ case "devnet":
2083
2084
  case "dev":
2084
2085
  this.walletUrl = "https://devnet.cantonloop.com";
2085
2086
  this.apiUrl = "https://devnet.cantonloop.com";
2086
2087
  break;
2088
+ case "testnet":
2087
2089
  case "test":
2088
2090
  this.walletUrl = "https://testnet.cantonloop.com";
2089
2091
  this.apiUrl = "https://testnet.cantonloop.com";
2090
2092
  break;
2093
+ case "mainnet":
2091
2094
  case "main":
2092
2095
  this.walletUrl = "https://cantonloop.com";
2093
2096
  this.apiUrl = "https://cantonloop.com";
@@ -2161,7 +2164,16 @@ class Connection {
2161
2164
  if (!response.ok) {
2162
2165
  throw new Error("Session verification failed.");
2163
2166
  }
2164
- 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;
2165
2177
  }
2166
2178
  websocketUrl(ticketId) {
2167
2179
  return `${this.network === "local" ? "ws" : "wss"}://${this.apiUrl.replace("https://", "").replace("http://", "")}/api/v1/.connect/pair/ws/${ticketId}`;
@@ -2200,15 +2212,17 @@ function generateRequestId() {
2200
2212
  class Provider {
2201
2213
  connection;
2202
2214
  party_id;
2215
+ public_key;
2203
2216
  auth_token;
2204
2217
  requests = new Map;
2205
2218
  requestTimeout = 30000;
2206
- constructor({ connection, party_id, auth_token, requestTimeout }) {
2219
+ constructor({ connection, party_id, public_key, auth_token, requestTimeout }) {
2207
2220
  if (!connection) {
2208
2221
  throw new Error("Provider requires a connection object.");
2209
2222
  }
2210
2223
  this.connection = connection;
2211
2224
  this.party_id = party_id;
2225
+ this.public_key = public_key;
2212
2226
  this.auth_token = auth_token;
2213
2227
  this.requestTimeout = requestTimeout || 30000;
2214
2228
  }
@@ -2294,21 +2308,20 @@ class LoopSDK {
2294
2308
  const existingConnectionRaw = localStorage.getItem("loop_connect");
2295
2309
  if (existingConnectionRaw) {
2296
2310
  try {
2297
- console.log("Existing connection raw:", existingConnectionRaw);
2298
- const { ticketId, authToken, partyId } = JSON.parse(existingConnectionRaw);
2299
- if (authToken && partyId) {
2311
+ const { ticketId, authToken, partyId, publicKey } = JSON.parse(existingConnectionRaw);
2312
+ if (authToken && partyId && publicKey) {
2300
2313
  try {
2301
2314
  const verifiedAccount = await this.connection.verifySession(authToken);
2302
2315
  if (verifiedAccount.party_id === partyId) {
2303
- 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 });
2304
2317
  this.onAccept?.(this.provider);
2305
2318
  if (ticketId) {
2306
2319
  this.connection.connectWebSocket(ticketId, this.handleWebSocketMessage.bind(this));
2307
2320
  }
2308
2321
  return;
2309
2322
  }
2310
- } catch (error) {
2311
- 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);
2312
2325
  }
2313
2326
  }
2314
2327
  if (ticketId) {
@@ -2323,7 +2336,7 @@ class LoopSDK {
2323
2336
  }
2324
2337
  localStorage.removeItem("loop_connect");
2325
2338
  }
2326
- const sessionId = this.generateSessionId();
2339
+ const sessionId = generateRequestId();
2327
2340
  try {
2328
2341
  const { ticket_id: ticketId } = await this.connection.getTicket(this.appName, sessionId, this.version);
2329
2342
  this.ticketId = ticketId;
@@ -2337,31 +2350,27 @@ class LoopSDK {
2337
2350
  }
2338
2351
  }
2339
2352
  handleWebSocketMessage(event) {
2340
- console.log("Received message:", event.data);
2341
2353
  const message = JSON.parse(event.data);
2342
2354
  if (message.type === "handshake_accept" /* HANDSHAKE_ACCEPT */) {
2343
- const { authToken, partyId } = message.payload || {};
2344
- console.log("Handshake accepted, token and account received.", authToken, partyId);
2345
- if (authToken && partyId) {
2346
- this.provider = new Provider({ connection: this.connection, party_id: partyId, auth_token: authToken });
2347
- 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 });
2348
2358
  const connectionInfoRaw = localStorage.getItem("loop_connect");
2349
2359
  if (connectionInfoRaw) {
2350
2360
  try {
2351
2361
  const connectionInfo = JSON.parse(connectionInfoRaw);
2352
2362
  connectionInfo.authToken = authToken;
2353
2363
  connectionInfo.partyId = partyId;
2364
+ connectionInfo.publicKey = publicKey;
2354
2365
  localStorage.setItem("loop_connect", JSON.stringify(connectionInfo));
2355
2366
  this.onAccept?.(this.provider);
2356
2367
  this.hideQrCode();
2357
- this.connection?.connectWebSocket(connectionInfo.ticketId, this.handleWebSocketMessage.bind(this));
2358
2368
  } catch (error) {
2359
2369
  console.error("Failed to update local storage with auth token.", error);
2360
2370
  }
2361
2371
  }
2362
2372
  }
2363
2373
  } else if (message.type === "handshake_reject" /* HANDSHAKE_REJECT */) {
2364
- console.log("Connection rejected.");
2365
2374
  localStorage.removeItem("loop_connect");
2366
2375
  this.connection?.ws?.close();
2367
2376
  this.onReject?.();
@@ -2416,9 +2425,6 @@ class LoopSDK {
2416
2425
  this.overlay = null;
2417
2426
  }
2418
2427
  }
2419
- generateSessionId() {
2420
- return crypto.randomUUID();
2421
- }
2422
2428
  }
2423
2429
  var loop = new LoopSDK;
2424
2430
  export {
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
- "author": "hello@fivenorth.io",
3
2
  "name": "@fivenorth/loop-sdk",
4
- "version": "0.1.0",
5
- "module": "dist/index.js",
3
+ "version": "0.1.2",
4
+ "author": "hello@fivenorth.io",
6
5
  "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "type": "module",
9
- "scripts": {
10
- "build": "bun build ./src/index.ts --outdir ./dist",
11
- "prepublishOnly": "bun run build",
12
- "start": "bun run src/server.ts"
13
- },
6
+ "module": "dist/index.js",
14
7
  "devDependencies": {
15
8
  "@types/bun": "latest"
16
9
  },
17
10
  "peerDependencies": {
18
11
  "typescript": "^5"
19
12
  },
20
- "dependencies": {
21
- "@types/qrcode": "^1.5.5",
22
- "qrcode": "^1.5.4"
23
- },
24
13
  "files": [
25
14
  "dist"
26
15
  ],
27
16
  "publishConfig": {
28
17
  "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "bun build ./src/index.ts --outdir ./dist",
21
+ "prepublishOnly": "bun run build",
22
+ "start": "bun run src/server.ts"
23
+ },
24
+ "type": "module",
25
+ "types": "dist/index.d.ts",
26
+ "dependencies": {
27
+ "@types/qrcode": "^1.5.6",
28
+ "qrcode": "^1.5.4"
29
29
  }
30
30
  }