@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.
- package/README.md +135 -3
- package/dist/index.js +23 -20
- 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
|
-
|
|
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
|
-
|
|
11
|
+
## Quick overview
|
|
12
12
|
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2301
|
-
|
|
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 (
|
|
2314
|
-
console.
|
|
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 =
|
|
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
|
-
|
|
2348
|
-
|
|
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 {
|