@fogo/sessions-sdk 0.0.11 → 0.0.13

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/cjs/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.sendTransfer = exports.SessionResultType = exports.getDomainRecordAddress = exports.AuthorizedTokens = exports.AuthorizedProgramsType = exports.getSessionAccount = exports.reestablishSession = exports.replaceSession = exports.establishSession = exports.createSolanaWalletAdapter = exports.TransactionResultType = void 0;
6
+ exports.verifyLogInToken = exports.createLogInToken = exports.sendTransfer = exports.SessionResultType = exports.getDomainRecordAddress = exports.AuthorizedTokens = exports.AuthorizedProgramsType = exports.getSessionAccount = exports.reestablishSession = exports.revokeSession = exports.replaceSession = exports.establishSession = exports.TransactionResultType = exports.createSolanaWalletAdapter = void 0;
7
7
  const anchor_1 = require("@coral-xyz/anchor");
8
8
  const sessions_idls_1 = require("@fogo/sessions-idls");
9
9
  const mpl_token_metadata_1 = require("@metaplex-foundation/mpl-token-metadata");
@@ -15,22 +15,26 @@ const kit_1 = require("@solana/kit");
15
15
  const spl_token_1 = require("@solana/spl-token");
16
16
  const web3_js_1 = require("@solana/web3.js");
17
17
  const bn_js_1 = __importDefault(require("bn.js"));
18
+ const bs58_1 = __importDefault(require("bs58"));
18
19
  const zod_1 = require("zod");
19
20
  const adapter_js_1 = require("./adapter.js");
21
+ const crypto_js_1 = require("./crypto.js");
20
22
  var adapter_js_2 = require("./adapter.js");
21
- Object.defineProperty(exports, "TransactionResultType", { enumerable: true, get: function () { return adapter_js_2.TransactionResultType; } });
22
23
  Object.defineProperty(exports, "createSolanaWalletAdapter", { enumerable: true, get: function () { return adapter_js_2.createSolanaWalletAdapter; } });
24
+ Object.defineProperty(exports, "TransactionResultType", { enumerable: true, get: function () { return adapter_js_2.TransactionResultType; } });
23
25
  const MESSAGE_HEADER = `Fogo Sessions:
24
26
  Signing this intent will allow this app to interact with your on-chain balances. Please make sure you trust this app and the domain in the message matches the domain of the current web application.
25
27
  `;
26
28
  const UNLIMITED_TOKEN_PERMISSIONS_VALUE = "this app may spend any amount of any token";
27
29
  const TOKENLESS_PERMISSIONS_VALUE = "this app may not spend any tokens";
28
30
  const CURRENT_MAJOR = "0";
29
- const CURRENT_MINOR = "1";
31
+ const CURRENT_MINOR = "2";
30
32
  const CURRENT_INTENT_TRANSFER_MAJOR = "0";
31
33
  const CURRENT_INTENT_TRANSFER_MINOR = "1";
32
34
  const establishSession = async (options) => {
33
- const sessionKey = await (0, kit_1.generateKeyPair)();
35
+ const sessionKey = options.createUnsafeExtractableSessionKey
36
+ ? await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])
37
+ : await (0, kit_1.generateKeyPair)();
34
38
  if (options.unlimited) {
35
39
  return sendSessionEstablishTransaction(options, sessionKey, await Promise.all([
36
40
  buildIntentInstruction(options, sessionKey),
@@ -73,6 +77,24 @@ const replaceSession = async (options) => (0, exports.establishSession)({
73
77
  walletPublicKey: options.session.walletPublicKey,
74
78
  });
75
79
  exports.replaceSession = replaceSession;
80
+ const revokeSession = async (options) => {
81
+ if (options.session.sessionInfo.minor >= 2) {
82
+ const instruction = await new sessions_idls_1.SessionManagerProgram(new anchor_1.AnchorProvider(options.adapter.connection, {}, {})).methods
83
+ .revokeSession()
84
+ .accounts({
85
+ sponsor: options.session.sessionInfo.sponsor,
86
+ session: options.session.sessionPublicKey,
87
+ })
88
+ .instruction();
89
+ return options.adapter.sendTransaction(options.session.sessionKey, [
90
+ instruction,
91
+ ]);
92
+ }
93
+ else {
94
+ return;
95
+ }
96
+ };
97
+ exports.revokeSession = revokeSession;
76
98
  const reestablishSession = async (adapter, walletPublicKey, sessionKey) => createSession(adapter, walletPublicKey, sessionKey);
77
99
  exports.reestablishSession = reestablishSession;
78
100
  const getSessionAccount = async (connection, sessionPublicKey) => {
@@ -98,49 +120,108 @@ const createSession = async (adapter, walletPublicKey, sessionKey) => {
98
120
  };
99
121
  const sessionInfoSchema = zod_1.z
100
122
  .object({
101
- session_info: zod_1.z.object({
102
- authorized_programs: zod_1.z.union([
103
- zod_1.z.object({
104
- Specific: zod_1.z.object({
105
- 0: zod_1.z.array(zod_1.z.object({
106
- program_id: zod_1.z.instanceof(web3_js_1.PublicKey),
107
- signer_pda: zod_1.z.instanceof(web3_js_1.PublicKey),
108
- })),
123
+ session_info: zod_1.z.union([
124
+ zod_1.z.object({
125
+ V1: zod_1.z.object({
126
+ "0": zod_1.z.object({
127
+ authorized_programs: zod_1.z.union([
128
+ zod_1.z.object({
129
+ Specific: zod_1.z.object({
130
+ 0: zod_1.z.array(zod_1.z.object({
131
+ program_id: zod_1.z.instanceof(web3_js_1.PublicKey),
132
+ signer_pda: zod_1.z.instanceof(web3_js_1.PublicKey),
133
+ })),
134
+ }),
135
+ }),
136
+ zod_1.z.object({
137
+ All: zod_1.z.object({}),
138
+ }),
139
+ ]),
140
+ authorized_tokens: zod_1.z.union([
141
+ zod_1.z.object({ Specific: zod_1.z.object({}) }),
142
+ zod_1.z.object({ All: zod_1.z.object({}) }),
143
+ ]),
144
+ expiration: zod_1.z.instanceof(bn_js_1.default),
145
+ extra: zod_1.z.object({
146
+ 0: zod_1.z.unknown(),
147
+ }),
148
+ user: zod_1.z.instanceof(web3_js_1.PublicKey),
109
149
  }),
110
150
  }),
111
- zod_1.z.object({
112
- All: zod_1.z.object({}),
151
+ }),
152
+ zod_1.z.object({
153
+ V2: zod_1.z.object({
154
+ "0": zod_1.z.union([
155
+ zod_1.z.object({
156
+ Revoked: zod_1.z.instanceof(bn_js_1.default),
157
+ }),
158
+ zod_1.z.object({
159
+ Active: zod_1.z.object({
160
+ "0": zod_1.z.object({
161
+ authorized_programs: zod_1.z.union([
162
+ zod_1.z.object({
163
+ Specific: zod_1.z.object({
164
+ 0: zod_1.z.array(zod_1.z.object({
165
+ program_id: zod_1.z.instanceof(web3_js_1.PublicKey),
166
+ signer_pda: zod_1.z.instanceof(web3_js_1.PublicKey),
167
+ })),
168
+ }),
169
+ }),
170
+ zod_1.z.object({
171
+ All: zod_1.z.object({}),
172
+ }),
173
+ ]),
174
+ authorized_tokens: zod_1.z.union([
175
+ zod_1.z.object({ Specific: zod_1.z.object({}) }),
176
+ zod_1.z.object({ All: zod_1.z.object({}) }),
177
+ ]),
178
+ expiration: zod_1.z.instanceof(bn_js_1.default),
179
+ extra: zod_1.z.object({
180
+ 0: zod_1.z.unknown(),
181
+ }),
182
+ user: zod_1.z.instanceof(web3_js_1.PublicKey),
183
+ }),
184
+ }),
185
+ }),
186
+ ]),
113
187
  }),
114
- ]),
115
- authorized_tokens: zod_1.z.union([
116
- zod_1.z.object({ Specific: zod_1.z.object({}) }),
117
- zod_1.z.object({ All: zod_1.z.object({}) }),
118
- ]),
119
- expiration: zod_1.z.instanceof(bn_js_1.default),
120
- extra: zod_1.z.object({
121
- 0: zod_1.z.unknown(),
122
188
  }),
123
- major: zod_1.z.number(),
124
- minor: zod_1.z.number(),
125
- user: zod_1.z.instanceof(web3_js_1.PublicKey),
126
- }),
189
+ ]),
190
+ major: zod_1.z.number(),
191
+ sponsor: zod_1.z.instanceof(web3_js_1.PublicKey),
127
192
  })
128
- .transform(({ session_info }) => ({
129
- authorizedPrograms: "All" in session_info.authorized_programs
130
- ? AuthorizedPrograms.All()
131
- : AuthorizedPrograms.Specific(session_info.authorized_programs.Specific[0].map(({ program_id, signer_pda }) => ({
132
- programId: program_id,
133
- signerPda: signer_pda,
134
- }))),
135
- authorizedTokens: "All" in session_info.authorized_tokens
136
- ? AuthorizedTokens.All
137
- : AuthorizedTokens.Specific,
138
- expiration: new Date(Number(session_info.expiration) * 1000),
139
- extra: session_info.extra[0],
140
- major: session_info.major,
141
- minor: session_info.minor,
142
- user: session_info.user,
143
- }));
193
+ .transform(({ session_info, major, sponsor }) => {
194
+ let activeSessionInfo;
195
+ let minor;
196
+ if ("V1" in session_info) {
197
+ activeSessionInfo = session_info.V1["0"];
198
+ minor = 1;
199
+ }
200
+ else if ("Active" in session_info.V2["0"]) {
201
+ activeSessionInfo = session_info.V2["0"].Active["0"];
202
+ minor = 2;
203
+ }
204
+ else {
205
+ return;
206
+ }
207
+ return {
208
+ authorizedPrograms: "All" in activeSessionInfo.authorized_programs
209
+ ? AuthorizedPrograms.All()
210
+ : AuthorizedPrograms.Specific(activeSessionInfo.authorized_programs.Specific[0].map(({ program_id, signer_pda }) => ({
211
+ programId: program_id,
212
+ signerPda: signer_pda,
213
+ }))),
214
+ authorizedTokens: "All" in activeSessionInfo.authorized_tokens
215
+ ? AuthorizedTokens.All
216
+ : AuthorizedTokens.Specific,
217
+ expiration: new Date(Number(activeSessionInfo.expiration) * 1000),
218
+ extra: activeSessionInfo.extra[0],
219
+ major: major,
220
+ minor: minor,
221
+ user: activeSessionInfo.user,
222
+ sponsor,
223
+ };
224
+ });
144
225
  var AuthorizedProgramsType;
145
226
  (function (AuthorizedProgramsType) {
146
227
  AuthorizedProgramsType[AuthorizedProgramsType["All"] = 0] = "All";
@@ -193,6 +274,36 @@ const getTokenInfo = async (adapter, limits) => {
193
274
  };
194
275
  }));
195
276
  };
277
+ const serializeU16LE = (value) => {
278
+ const result = new ArrayBuffer(2);
279
+ new DataView(result).setUint16(0, value, true); // littleEndian = true
280
+ return new Uint8Array(result);
281
+ };
282
+ // Some wallets add a prefix to the messag before signing, for example Ledger through Phantom
283
+ const addOffchainMessagePrefixToMessageIfNeeded = async (walletPublicKey, signature, message) => {
284
+ const publicKey = await crypto.subtle.importKey("raw", walletPublicKey.toBytes(), { name: "Ed25519" }, true, ["verify"]);
285
+ if (await (0, kit_1.verifySignature)(publicKey, signature, message)) {
286
+ return message;
287
+ }
288
+ else {
289
+ // Source: https://github.com/anza-xyz/solana-sdk/blob/master/offchain-message/src/lib.rs#L162
290
+ const messageWithOffchainMessagePrefix = Uint8Array.from([
291
+ // eslint-disable-next-line unicorn/number-literal-case
292
+ 0xff,
293
+ ...new TextEncoder().encode("solana offchain"),
294
+ 0,
295
+ 1,
296
+ ...serializeU16LE(message.length),
297
+ ...message,
298
+ ]);
299
+ if (await (0, kit_1.verifySignature)(publicKey, signature, messageWithOffchainMessagePrefix)) {
300
+ return messageWithOffchainMessagePrefix;
301
+ }
302
+ else {
303
+ throw new Error("The signature provided by the browser wallet is not valid");
304
+ }
305
+ }
306
+ };
196
307
  const buildIntentInstruction = async (options, sessionKey, tokens) => {
197
308
  const message = await buildMessage({
198
309
  chainId: options.adapter.chainId,
@@ -206,7 +317,7 @@ const buildIntentInstruction = async (options, sessionKey, tokens) => {
206
317
  return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
207
318
  publicKey: options.walletPublicKey.toBytes(),
208
319
  signature: intentSignature,
209
- message: message,
320
+ message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
210
321
  });
211
322
  };
212
323
  const buildMessage = async (body) => new TextEncoder().encode([
@@ -356,7 +467,7 @@ const buildTransferIntentInstruction = async (program, options, symbol) => {
356
467
  return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
357
468
  publicKey: options.walletPublicKey.toBytes(),
358
469
  signature: intentSignature,
359
- message: message,
470
+ message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
360
471
  });
361
472
  };
362
473
  const getNonce = async (program, walletPublicKey) => {
@@ -366,3 +477,46 @@ const getNonce = async (program, walletPublicKey) => {
366
477
  });
367
478
  return program.account.nonce.fetchNullable(noncePda);
368
479
  };
480
+ const loginTokenPayloadSchema = zod_1.z.object({
481
+ iat: zod_1.z.number(),
482
+ sessionPublicKey: zod_1.z.string(),
483
+ });
484
+ /**
485
+ * Create a login token signed with the session key
486
+ * @param session - The session to create a login token for
487
+ * @returns The login token
488
+ */
489
+ const createLogInToken = async (session) => {
490
+ const payload = {
491
+ // ...we can pass any arbitrary data we want to sign here...
492
+ iat: Date.now(),
493
+ sessionPublicKey: session.sessionPublicKey.toBase58(),
494
+ };
495
+ const message = JSON.stringify(payload);
496
+ // Sign the payload with the session private key
497
+ const signature = await (0, crypto_js_1.signMessageWithKey)(session.sessionKey, message);
498
+ // Return base58(message) + base58(signature)
499
+ return `${bs58_1.default.encode(new TextEncoder().encode(message))}.${signature}`;
500
+ };
501
+ exports.createLogInToken = createLogInToken;
502
+ /**
503
+ * Verify a login token
504
+ * @param token - The login token to verify against the session public key
505
+ * @param connection - The connection to use to get the session account
506
+ * @returns The session account if the token is valid, otherwise undefined
507
+ */
508
+ const verifyLogInToken = async (token, connection) => {
509
+ const [rawMessage, signature] = token.split(".");
510
+ if (!rawMessage || !signature)
511
+ return;
512
+ // Decode + parse payload
513
+ const messageStr = new TextDecoder().decode(bs58_1.default.decode(rawMessage));
514
+ const payload = loginTokenPayloadSchema.parse(JSON.parse(messageStr));
515
+ // Verify signature with sessionPublicKey
516
+ const sessionCryptoKey = await (0, crypto_js_1.importKey)(payload.sessionPublicKey);
517
+ const isValid = await (0, crypto_js_1.verifyMessageWithKey)(sessionCryptoKey, messageStr, signature);
518
+ if (!isValid)
519
+ return;
520
+ return (0, exports.getSessionAccount)(connection, new web3_js_1.PublicKey(payload.sessionPublicKey));
521
+ };
522
+ exports.verifyLogInToken = verifyLogInToken;
package/esm/adapter.js CHANGED
@@ -71,7 +71,12 @@ const getSponsor = async (options, domain) => {
71
71
  const url = new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER);
72
72
  url.searchParams.set("domain", domain);
73
73
  const response = await fetch(url);
74
- return new PublicKey(z.string().parse(await response.text()));
74
+ if (response.status === 200) {
75
+ return new PublicKey(z.string().parse(await response.text()));
76
+ }
77
+ else {
78
+ throw new PaymasterResponseError(response.status, await response.text());
79
+ }
75
80
  }
76
81
  };
77
82
  const sponsorAndSendResponseSchema = z
@@ -83,7 +88,9 @@ const sponsorAndSendResponseSchema = z
83
88
  z.object({
84
89
  type: z.literal("failed"),
85
90
  signature: z.string(),
86
- error: z.object({}),
91
+ error: z.object({
92
+ InstructionError: z.tuple([z.number(), z.unknown()]),
93
+ }),
87
94
  }),
88
95
  ])
89
96
  .transform((data) => {
@@ -97,7 +104,6 @@ const sendToPaymaster = async (options, transaction, domain) => {
97
104
  }
98
105
  else {
99
106
  const url = new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER);
100
- url.searchParams.set("confirm", "true");
101
107
  url.searchParams.set("domain", domain);
102
108
  const response = await fetch(url, {
103
109
  method: "POST",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Sign a message using a CryptoKeyPair session key
3
+ * @param publicPrivateKeyPair - The public private key pair to sign the message with
4
+ * @param message - The message to sign
5
+ * @returns The signature of the message
6
+ */
7
+ export declare const signMessageWithKey: (publicPrivateKeyPair: CryptoKeyPair, message: string) => Promise<string>;
8
+ /**
9
+ * Verify a message with a CryptoKey public key
10
+ * @param publicKey - The public key of the session key
11
+ * @param message - The message to verify
12
+ * @param signature - The signature to verify
13
+ * @returns True if the message and signature are valid, false otherwise
14
+ */
15
+ export declare const verifyMessageWithKey: (publicKey: CryptoKey, message: string, signature: string) => Promise<boolean>;
16
+ /**
17
+ * Import a public key into a CryptoKey
18
+ * @param publicKey - The public key to import
19
+ * @returns The imported CryptoKey
20
+ */
21
+ export declare const importKey: (publicKey: string) => Promise<CryptoKey>;
package/esm/crypto.js ADDED
@@ -0,0 +1,30 @@
1
+ import bs58 from "bs58";
2
+ /**
3
+ * Sign a message using a CryptoKeyPair session key
4
+ * @param publicPrivateKeyPair - The public private key pair to sign the message with
5
+ * @param message - The message to sign
6
+ * @returns The signature of the message
7
+ */
8
+ export const signMessageWithKey = async (publicPrivateKeyPair, message) => {
9
+ const signature = await crypto.subtle.sign({ name: "Ed25519" }, publicPrivateKeyPair.privateKey, new TextEncoder().encode(message));
10
+ return bs58.encode(new Uint8Array(signature));
11
+ };
12
+ /**
13
+ * Verify a message with a CryptoKey public key
14
+ * @param publicKey - The public key of the session key
15
+ * @param message - The message to verify
16
+ * @param signature - The signature to verify
17
+ * @returns True if the message and signature are valid, false otherwise
18
+ */
19
+ export const verifyMessageWithKey = async (publicKey, message, signature) => {
20
+ const isValid = await crypto.subtle.verify({ name: "Ed25519" }, publicKey, bs58.decode(signature), new TextEncoder().encode(message));
21
+ return isValid;
22
+ };
23
+ /**
24
+ * Import a public key into a CryptoKey
25
+ * @param publicKey - The public key to import
26
+ * @returns The imported CryptoKey
27
+ */
28
+ export const importKey = async (publicKey) => {
29
+ return await crypto.subtle.importKey("raw", new Uint8Array(bs58.decode(publicKey)), { name: "Ed25519" }, false, ["verify"]);
30
+ };