@fivenorth/loop-sdk 0.11.0 → 0.11.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 +62 -1
- package/dist/connection.js +290 -0
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +35 -0
- package/dist/extensions/usdc/index.js +52 -0
- package/dist/extensions/usdc/types.js +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -52
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +249 -0
- package/dist/server/index.js +116 -35343
- package/dist/server/signer.js +56 -0
- package/dist/server.js +38 -0
- package/dist/session.js +92 -0
- package/dist/types.js +10 -0
- package/dist/wallet.js +22 -0
- package/package.json +9 -6
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as forge from 'node-forge';
|
|
2
|
+
export const getSigner = (privateKeyHex, partyId) => {
|
|
3
|
+
return new Signer(privateKeyHex, partyId);
|
|
4
|
+
};
|
|
5
|
+
export class Signer {
|
|
6
|
+
privateKey;
|
|
7
|
+
publicKey;
|
|
8
|
+
publicKeyHex;
|
|
9
|
+
partyId;
|
|
10
|
+
constructor(privateKeyHex, partyId) {
|
|
11
|
+
if (!privateKeyHex || !partyId) {
|
|
12
|
+
throw new Error('Private key and party ID are required');
|
|
13
|
+
}
|
|
14
|
+
this.privateKey = forge.util.hexToBytes(privateKeyHex);
|
|
15
|
+
this.partyId = partyId;
|
|
16
|
+
const publicKey = forge.pki.ed25519.publicKeyFromPrivateKey({
|
|
17
|
+
privateKey: this.privateKey,
|
|
18
|
+
});
|
|
19
|
+
this.publicKey = publicKey;
|
|
20
|
+
this.publicKeyHex = forge.util.bytesToHex(publicKey);
|
|
21
|
+
}
|
|
22
|
+
getPublicKey() {
|
|
23
|
+
return this.publicKeyHex;
|
|
24
|
+
}
|
|
25
|
+
signMessage(message) {
|
|
26
|
+
return forge.pki.ed25519.sign({
|
|
27
|
+
message: message,
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
privateKey: this.privateKey,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
signMessageAsHex(message) {
|
|
33
|
+
const signature = forge.pki.ed25519.sign({
|
|
34
|
+
message: message,
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
privateKey: this.privateKey,
|
|
37
|
+
});
|
|
38
|
+
return forge.util.bytesToHex(signature);
|
|
39
|
+
}
|
|
40
|
+
getPartyId() {
|
|
41
|
+
return this.partyId;
|
|
42
|
+
}
|
|
43
|
+
// sign the transaction hash in base64 format and return the signature in hex format
|
|
44
|
+
signTransactionHash(transactionHash) {
|
|
45
|
+
if (!transactionHash) {
|
|
46
|
+
throw new Error('Transaction hash is required');
|
|
47
|
+
}
|
|
48
|
+
// Now we will sign the transaction hash
|
|
49
|
+
const signedRequest = forge.pki.ed25519.sign({
|
|
50
|
+
message: forge.util.decode64(transactionHash),
|
|
51
|
+
encoding: 'binary',
|
|
52
|
+
privateKey: this.privateKey,
|
|
53
|
+
});
|
|
54
|
+
return forge.util.bytesToHex(signedRequest);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
const server = Bun.serve({
|
|
3
|
+
port: 3030,
|
|
4
|
+
async fetch(req) {
|
|
5
|
+
const url = new URL(req.url);
|
|
6
|
+
const pathname = url.pathname;
|
|
7
|
+
// Serve the test page
|
|
8
|
+
if (pathname === '/') {
|
|
9
|
+
return new Response(Bun.file(join(import.meta.dir, '..', '/demo/test.html')));
|
|
10
|
+
}
|
|
11
|
+
// Intercept the request for the SDK and compile it on the fly
|
|
12
|
+
if (pathname === '/dist/index.js') {
|
|
13
|
+
console.log('Bundling src/index.ts for development...');
|
|
14
|
+
const build = await Bun.build({
|
|
15
|
+
entrypoints: [join(import.meta.dir, 'index.ts')],
|
|
16
|
+
target: 'browser',
|
|
17
|
+
sourcemap: 'inline', // Good for debugging
|
|
18
|
+
});
|
|
19
|
+
if (!build.success) {
|
|
20
|
+
console.error("Build failed:");
|
|
21
|
+
for (const message of build.logs) {
|
|
22
|
+
console.error(message);
|
|
23
|
+
}
|
|
24
|
+
return new Response('Build failed', { status: 500 });
|
|
25
|
+
}
|
|
26
|
+
// Serve the first build artifact (the JS file)
|
|
27
|
+
return new Response(build.outputs[0], {
|
|
28
|
+
headers: { 'Content-Type': 'application/javascript; charset=utf-8' },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return new Response('Not Found', { status: 404 });
|
|
32
|
+
},
|
|
33
|
+
error(error) {
|
|
34
|
+
console.error(error);
|
|
35
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
console.log(`Listening on http://localhost:${server.port}`);
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { generateRequestId } from './provider';
|
|
2
|
+
const STORAGE_KEY_LOOP_CONNECT = 'loop_connect';
|
|
3
|
+
/**
|
|
4
|
+
* Session LifeCycle:
|
|
5
|
+
* 1. Initialize a new session with a session id
|
|
6
|
+
* 2. Set the ticket id when we exchange session id/appname, and user approve it and now we have a ticket id
|
|
7
|
+
* 3. Set the session as authorized when we succesfully validate the session with the backend
|
|
8
|
+
* 4. Save the session to localStorage
|
|
9
|
+
* 5. Reset the session when we need to start a new session
|
|
10
|
+
* 6. Validate the session when we need to check if the session is valid
|
|
11
|
+
* 7. Is authorized means the session is valid and the user is authenticated
|
|
12
|
+
* 8. Save the session to localStorage
|
|
13
|
+
*/
|
|
14
|
+
export class SessionInfo {
|
|
15
|
+
sessionId;
|
|
16
|
+
ticketId;
|
|
17
|
+
authToken;
|
|
18
|
+
partyId;
|
|
19
|
+
publicKey;
|
|
20
|
+
email;
|
|
21
|
+
userApiKey;
|
|
22
|
+
_isAuthorized = false;
|
|
23
|
+
constructor({ sessionId, ticketId, authToken, partyId, publicKey, email, userApiKey }) {
|
|
24
|
+
this.sessionId = sessionId;
|
|
25
|
+
this.ticketId = ticketId;
|
|
26
|
+
this.authToken = authToken;
|
|
27
|
+
this.partyId = partyId;
|
|
28
|
+
this.publicKey = publicKey;
|
|
29
|
+
this.email = email;
|
|
30
|
+
this.userApiKey = userApiKey;
|
|
31
|
+
}
|
|
32
|
+
// set the ticket id when we exchange session id/appname, and user approve it and now we have a ticket id
|
|
33
|
+
setTicketId(ticketId) {
|
|
34
|
+
this.ticketId = ticketId;
|
|
35
|
+
this.save();
|
|
36
|
+
}
|
|
37
|
+
// set the session as authorized when we succesfully validate the session with the backend
|
|
38
|
+
authorized() {
|
|
39
|
+
if (this.ticketId === undefined || this.sessionId === undefined || this.authToken === undefined || this.partyId === undefined || this.publicKey === undefined) {
|
|
40
|
+
throw new Error('Session cannot be authorized without all required fields.');
|
|
41
|
+
}
|
|
42
|
+
this._isAuthorized = true;
|
|
43
|
+
}
|
|
44
|
+
// is pre authorized means the session is initialized and the ticket id is set together with auth and user information but we haven't validated the session with the backend yet
|
|
45
|
+
isPreAuthorized() {
|
|
46
|
+
return !this._isAuthorized && this.ticketId !== undefined && this.sessionId !== undefined && this.authToken !== undefined && this.partyId !== undefined && this.publicKey !== undefined;
|
|
47
|
+
}
|
|
48
|
+
// is authorized means the session is valid and the user is authenticated
|
|
49
|
+
isAuthorized() {
|
|
50
|
+
return this._isAuthorized;
|
|
51
|
+
}
|
|
52
|
+
// save persisted session info to localStorage
|
|
53
|
+
save() {
|
|
54
|
+
localStorage.setItem('loop_connect', this.toJson());
|
|
55
|
+
}
|
|
56
|
+
reset() {
|
|
57
|
+
localStorage.removeItem(STORAGE_KEY_LOOP_CONNECT);
|
|
58
|
+
this.sessionId = generateRequestId();
|
|
59
|
+
this._isAuthorized = false;
|
|
60
|
+
this.ticketId = undefined;
|
|
61
|
+
this.authToken = undefined;
|
|
62
|
+
this.partyId = undefined;
|
|
63
|
+
this.publicKey = undefined;
|
|
64
|
+
this.email = undefined;
|
|
65
|
+
}
|
|
66
|
+
static fromStorage() {
|
|
67
|
+
const existingConnectionRaw = localStorage.getItem(STORAGE_KEY_LOOP_CONNECT);
|
|
68
|
+
if (!existingConnectionRaw) {
|
|
69
|
+
return new SessionInfo({ sessionId: generateRequestId() });
|
|
70
|
+
}
|
|
71
|
+
let session = null;
|
|
72
|
+
try {
|
|
73
|
+
session = new SessionInfo(JSON.parse(existingConnectionRaw));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Failed to parse existing connection info, local storage is corrupted.', error);
|
|
77
|
+
localStorage.removeItem(STORAGE_KEY_LOOP_CONNECT);
|
|
78
|
+
session = new SessionInfo({ sessionId: generateRequestId() });
|
|
79
|
+
}
|
|
80
|
+
return session;
|
|
81
|
+
}
|
|
82
|
+
toJson() {
|
|
83
|
+
return JSON.stringify({
|
|
84
|
+
sessionId: this.sessionId,
|
|
85
|
+
ticketId: this.ticketId,
|
|
86
|
+
authToken: this.authToken,
|
|
87
|
+
partyId: this.partyId,
|
|
88
|
+
publicKey: this.publicKey,
|
|
89
|
+
email: this.email,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export var MessageType;
|
|
2
|
+
(function (MessageType) {
|
|
3
|
+
MessageType["HANDSHAKE_ACCEPT"] = "handshake_accept";
|
|
4
|
+
MessageType["HANDSHAKE_REJECT"] = "handshake_reject";
|
|
5
|
+
MessageType["RUN_TRANSACTION"] = "run_transaction";
|
|
6
|
+
MessageType["TRANSACTION_COMPLETED"] = "transaction_completed";
|
|
7
|
+
MessageType["SIGN_RAW_MESSAGE"] = "sign_raw_message";
|
|
8
|
+
MessageType["SIGN_RAW_MESSAGE_RESPONSE"] = "sign_raw_message_response";
|
|
9
|
+
MessageType["REJECT_REQUEST"] = "reject_request";
|
|
10
|
+
})(MessageType || (MessageType = {}));
|
package/dist/wallet.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { UsdcBridge } from './extensions/usdc';
|
|
2
|
+
export class LoopWallet {
|
|
3
|
+
getProvider;
|
|
4
|
+
extension;
|
|
5
|
+
constructor(getProvider) {
|
|
6
|
+
this.getProvider = getProvider;
|
|
7
|
+
this.extension = {
|
|
8
|
+
usdcBridge: new UsdcBridge(this.getProvider),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
requireProvider() {
|
|
12
|
+
const provider = this.getProvider();
|
|
13
|
+
if (!provider) {
|
|
14
|
+
throw new Error('SDK not connected. Call connect() and wait for acceptance first.');
|
|
15
|
+
}
|
|
16
|
+
return provider;
|
|
17
|
+
}
|
|
18
|
+
transfer(recipient, amount, instrument, options) {
|
|
19
|
+
const provider = this.requireProvider();
|
|
20
|
+
return provider.transfer(recipient, amount, instrument, options);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fivenorth/loop-sdk",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"author": "support@fivenorth.io",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
"@types/node-forge": "^1.3.14"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
|
-
"typescript": "^5"
|
|
12
|
+
"typescript": "^5",
|
|
13
|
+
"node-forge": "^1.3.3"
|
|
14
|
+
},
|
|
15
|
+
"peerDependenciesMeta": {
|
|
16
|
+
"node-forge": {
|
|
17
|
+
"optional": true
|
|
18
|
+
}
|
|
13
19
|
},
|
|
14
20
|
"files": [
|
|
15
21
|
"dist"
|
|
@@ -19,7 +25,7 @@
|
|
|
19
25
|
},
|
|
20
26
|
"repository": "github:fivenorth-io/loop-sdk",
|
|
21
27
|
"scripts": {
|
|
22
|
-
"build": "bun tsc && bun build ./src/index.ts
|
|
28
|
+
"build": "bun tsc && bun build ./src/index.ts --outdir ./dist --target browser --format esm",
|
|
23
29
|
"prepublishOnly": "bun run build",
|
|
24
30
|
"start": "bun run src/server.ts"
|
|
25
31
|
},
|
|
@@ -38,8 +44,5 @@
|
|
|
38
44
|
"dependencies": {
|
|
39
45
|
"@types/qrcode": "^1.5.6",
|
|
40
46
|
"qrcode": "^1.5.4"
|
|
41
|
-
},
|
|
42
|
-
"optionalDependencies": {
|
|
43
|
-
"node-forge": "^1.3.3"
|
|
44
47
|
}
|
|
45
48
|
}
|