@fogo/sessions-sdk 0.0.9 → 0.0.11

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/adapter.d.ts CHANGED
@@ -6,7 +6,7 @@ export type SessionAdapter = {
6
6
  connection: Connection;
7
7
  payer: PublicKey;
8
8
  domain: string;
9
- sendTransaction: (sessionKey: CryptoKeyPair, instructions: (TransactionInstruction | IInstruction)[] | VersionedTransaction | Transaction) => Promise<TransactionResult>;
9
+ sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | IInstruction)[] | VersionedTransaction | Transaction) => Promise<TransactionResult>;
10
10
  };
11
11
  export declare enum TransactionResultType {
12
12
  Success = 0,
@@ -31,7 +31,7 @@ export declare const createSolanaWalletAdapter: (options: {
31
31
  } & ({
32
32
  paymaster?: string | URL | undefined;
33
33
  } | {
34
- sendToPaymaster: (transaction: Transaction) => Promise<string>;
34
+ sendToPaymaster: (transaction: Transaction) => Promise<TransactionResult>;
35
35
  sponsor: PublicKey;
36
36
  })) => Promise<SessionAdapter>;
37
37
  export {};
package/cjs/adapter.js CHANGED
@@ -29,7 +29,8 @@ const TransactionResult = {
29
29
  };
30
30
  const createSolanaWalletAdapter = async (options) => {
31
31
  const addressLookupTables = await getAddressLookupTables(options.connection, options.addressLookupTableAddress);
32
- const sponsor = await getSponsor(options);
32
+ const domain = getDomain(options.domain);
33
+ const sponsor = await getSponsor(options, domain);
33
34
  return {
34
35
  connection: options.connection,
35
36
  payer: sponsor,
@@ -38,46 +39,71 @@ const createSolanaWalletAdapter = async (options) => {
38
39
  sendTransaction: async (sessionKey, instructions) => {
39
40
  const rpc = (0, kit_1.createSolanaRpc)(options.connection.rpcEndpoint);
40
41
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
41
- const sessionKeySigner = await (0, kit_1.createSignerFromKeyPair)(sessionKey);
42
- const transaction = Array.isArray(instructions)
43
- ? await (0, kit_1.partiallySignTransactionMessageWithSigners)((0, kit_1.pipe)((0, kit_1.createTransactionMessage)({ version: 0 }), (tx) => (0, kit_1.setTransactionMessageFeePayer)((0, compat_1.fromLegacyPublicKey)(sponsor), tx), (tx) => (0, kit_1.setTransactionMessageLifetimeUsingBlockhash)(latestBlockhash, tx), (tx) => (0, kit_1.appendTransactionMessageInstructions)(instructions.map((instruction) => instruction instanceof web3_js_1.TransactionInstruction
44
- ? (0, compat_1.fromLegacyTransactionInstruction)(instruction)
45
- : instruction), tx), (tx) => (0, kit_1.compressTransactionMessageUsingAddressLookupTables)(tx, Object.fromEntries(addressLookupTables?.map((table) => [
46
- (0, compat_1.fromLegacyPublicKey)(table.key),
47
- table.state.addresses.map((address) => (0, compat_1.fromLegacyPublicKey)(address)),
48
- ]) ?? [])), (tx) => (0, kit_1.addSignersToTransactionMessage)([sessionKeySigner], tx)))
49
- : await (0, kit_1.partiallySignTransaction)([sessionKey], instructions instanceof web3_js_1.VersionedTransaction
50
- ? (0, compat_1.fromVersionedTransaction)(instructions)
51
- : instructions);
52
- const signature = await sendToPaymaster(options, transaction);
53
- const lastValidBlockHeight = await rpc.getSlot().send();
54
- const confirmationResult = await options.connection.confirmTransaction({
55
- signature,
56
- blockhash: latestBlockhash.blockhash.toString(),
57
- lastValidBlockHeight: Number(lastValidBlockHeight),
58
- });
59
- return confirmationResult.value.err === null
60
- ? TransactionResult.Success(signature)
61
- : TransactionResult.Failed(signature, confirmationResult.value.err);
42
+ return await sendToPaymaster(options, await buildTransaction(latestBlockhash, sessionKey, sponsor, instructions, addressLookupTables), domain);
62
43
  },
63
44
  };
64
45
  };
65
46
  exports.createSolanaWalletAdapter = createSolanaWalletAdapter;
66
- const getSponsor = async (options) => {
47
+ const buildTransaction = async (latestBlockhash, sessionKey, sponsor, instructions, addressLookupTables) => {
48
+ const sessionKeySigner = sessionKey
49
+ ? await (0, kit_1.createSignerFromKeyPair)(sessionKey)
50
+ : undefined;
51
+ if (Array.isArray(instructions)) {
52
+ return (0, kit_1.partiallySignTransactionMessageWithSigners)((0, kit_1.pipe)((0, kit_1.createTransactionMessage)({ version: 0 }), (tx) => (0, kit_1.setTransactionMessageFeePayer)((0, compat_1.fromLegacyPublicKey)(sponsor), tx), (tx) => (0, kit_1.setTransactionMessageLifetimeUsingBlockhash)(latestBlockhash, tx), (tx) => (0, kit_1.appendTransactionMessageInstructions)(instructions.map((instruction) => instruction instanceof web3_js_1.TransactionInstruction
53
+ ? (0, compat_1.fromLegacyTransactionInstruction)(instruction)
54
+ : instruction), tx), (tx) => (0, kit_1.compressTransactionMessageUsingAddressLookupTables)(tx, Object.fromEntries(addressLookupTables?.map((table) => [
55
+ (0, compat_1.fromLegacyPublicKey)(table.key),
56
+ table.state.addresses.map((address) => (0, compat_1.fromLegacyPublicKey)(address)),
57
+ ]) ?? [])), (tx) => sessionKeySigner === undefined
58
+ ? tx
59
+ : (0, kit_1.addSignersToTransactionMessage)([sessionKeySigner], tx)));
60
+ }
61
+ else {
62
+ const tx = instructions instanceof web3_js_1.VersionedTransaction
63
+ ? (0, compat_1.fromVersionedTransaction)(instructions)
64
+ : instructions;
65
+ return sessionKey === undefined
66
+ ? tx
67
+ : (0, kit_1.partiallySignTransaction)([sessionKey], tx);
68
+ }
69
+ };
70
+ const getSponsor = async (options, domain) => {
67
71
  if ("sponsor" in options) {
68
72
  return options.sponsor;
69
73
  }
70
74
  else {
71
- const response = await fetch(new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER));
75
+ const url = new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER);
76
+ url.searchParams.set("domain", domain);
77
+ const response = await fetch(url);
72
78
  return new web3_js_1.PublicKey(zod_1.z.string().parse(await response.text()));
73
79
  }
74
80
  };
75
- const sendToPaymaster = async (options, transaction) => {
81
+ const sponsorAndSendResponseSchema = zod_1.z
82
+ .discriminatedUnion("type", [
83
+ zod_1.z.object({
84
+ type: zod_1.z.literal("success"),
85
+ signature: zod_1.z.string(),
86
+ }),
87
+ zod_1.z.object({
88
+ type: zod_1.z.literal("failed"),
89
+ signature: zod_1.z.string(),
90
+ error: zod_1.z.object({}),
91
+ }),
92
+ ])
93
+ .transform((data) => {
94
+ return data.type === "success"
95
+ ? TransactionResult.Success(data.signature)
96
+ : TransactionResult.Failed(data.signature, data.error);
97
+ });
98
+ const sendToPaymaster = async (options, transaction, domain) => {
76
99
  if ("sendToPaymaster" in options) {
77
100
  return options.sendToPaymaster(transaction);
78
101
  }
79
102
  else {
80
- const response = await fetch(new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER), {
103
+ const url = new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER);
104
+ url.searchParams.set("confirm", "true");
105
+ url.searchParams.set("domain", domain);
106
+ const response = await fetch(url, {
81
107
  method: "POST",
82
108
  headers: {
83
109
  "Content-Type": "application/json",
@@ -87,7 +113,7 @@ const sendToPaymaster = async (options, transaction) => {
87
113
  }),
88
114
  });
89
115
  if (response.status === 200) {
90
- return response.text();
116
+ return sponsorAndSendResponseSchema.parse(await response.json());
91
117
  }
92
118
  else {
93
119
  throw new PaymasterResponseError(response.status, await response.text());
package/cjs/index.d.ts CHANGED
@@ -290,3 +290,12 @@ export type Session = {
290
290
  sendTransaction: (instructions: Parameters<SessionAdapter["sendTransaction"]>[1]) => Promise<TransactionResult>;
291
291
  sessionInfo: z.infer<typeof sessionInfoSchema>;
292
292
  };
293
+ type SendTransferOptions = {
294
+ adapter: SessionAdapter;
295
+ walletPublicKey: PublicKey;
296
+ signMessage: (message: Uint8Array) => Promise<Uint8Array>;
297
+ mint: PublicKey;
298
+ amount: bigint;
299
+ recipient: PublicKey;
300
+ };
301
+ export declare const sendTransfer: (options: SendTransferOptions) => Promise<TransactionResult>;
package/cjs/index.js CHANGED
@@ -3,13 +3,14 @@ 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.SessionResultType = exports.getDomainRecordAddress = exports.AuthorizedTokens = exports.AuthorizedProgramsType = exports.getSessionAccount = exports.reestablishSession = exports.replaceSession = exports.establishSession = exports.createSolanaWalletAdapter = exports.TransactionResultType = void 0;
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;
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");
10
10
  const umi_1 = require("@metaplex-foundation/umi");
11
11
  const umi_bundle_defaults_1 = require("@metaplex-foundation/umi-bundle-defaults");
12
12
  const sha2_1 = require("@noble/hashes/sha2");
13
+ const compat_1 = require("@solana/compat");
13
14
  const kit_1 = require("@solana/kit");
14
15
  const spl_token_1 = require("@solana/spl-token");
15
16
  const web3_js_1 = require("@solana/web3.js");
@@ -26,6 +27,8 @@ const UNLIMITED_TOKEN_PERMISSIONS_VALUE = "this app may spend any amount of any
26
27
  const TOKENLESS_PERMISSIONS_VALUE = "this app may not spend any tokens";
27
28
  const CURRENT_MAJOR = "0";
28
29
  const CURRENT_MINOR = "1";
30
+ const CURRENT_INTENT_TRANSFER_MAJOR = "0";
31
+ const CURRENT_INTENT_TRANSFER_MINOR = "1";
29
32
  const establishSession = async (options) => {
30
33
  const sessionKey = await (0, kit_1.generateKeyPair)();
31
34
  if (options.unlimited) {
@@ -155,10 +158,11 @@ var AuthorizedTokens;
155
158
  AuthorizedTokens[AuthorizedTokens["All"] = 0] = "All";
156
159
  AuthorizedTokens[AuthorizedTokens["Specific"] = 1] = "Specific";
157
160
  })(AuthorizedTokens || (exports.AuthorizedTokens = AuthorizedTokens = {}));
158
- const SymbolOrMintType = {
159
- Symbol: "Symbol",
160
- Mint: "Mint",
161
- };
161
+ var SymbolOrMintType;
162
+ (function (SymbolOrMintType) {
163
+ SymbolOrMintType[SymbolOrMintType["Symbol"] = 0] = "Symbol";
164
+ SymbolOrMintType[SymbolOrMintType["Mint"] = 1] = "Mint";
165
+ })(SymbolOrMintType || (SymbolOrMintType = {}));
162
166
  const SymbolOrMint = {
163
167
  Symbol: (symbol) => ({
164
168
  type: SymbolOrMintType.Symbol,
@@ -303,3 +307,62 @@ const EstablishSessionResult = {
303
307
  error,
304
308
  }),
305
309
  };
310
+ const TRANSFER_MESSAGE_HEADER = `Fogo Transfer:
311
+ Signing this intent will transfer the tokens as described below.
312
+ `;
313
+ const sendTransfer = async (options) => {
314
+ const sourceAta = (0, spl_token_1.getAssociatedTokenAddressSync)(options.mint, options.walletPublicKey);
315
+ const destinationAta = (0, spl_token_1.getAssociatedTokenAddressSync)(options.mint, options.recipient);
316
+ const program = new sessions_idls_1.IntentTransferProgram(new anchor_1.AnchorProvider(options.adapter.connection, {}, {}));
317
+ const umi = (0, umi_bundle_defaults_1.createUmi)(options.adapter.connection.rpcEndpoint);
318
+ const metaplexMint = (0, umi_1.publicKey)(options.mint.toBase58());
319
+ const metadataAddress = (0, mpl_token_metadata_1.findMetadataPda)(umi, { mint: metaplexMint })[0];
320
+ const metadata = await (0, mpl_token_metadata_1.safeFetchMetadata)(umi, metadataAddress);
321
+ const symbol = metadata?.symbol ?? undefined;
322
+ return options.adapter.sendTransaction(undefined, [
323
+ (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(options.adapter.payer, destinationAta, options.recipient, options.mint),
324
+ await buildTransferIntentInstruction(program, options, symbol),
325
+ await program.methods
326
+ .sendTokens()
327
+ .accounts({
328
+ destination: destinationAta,
329
+ mint: options.mint,
330
+ source: sourceAta,
331
+ sponsor: options.adapter.payer,
332
+ // eslint-disable-next-line unicorn/no-null
333
+ metadata: symbol === undefined ? null : new web3_js_1.PublicKey(metadataAddress),
334
+ })
335
+ .instruction(),
336
+ ]);
337
+ };
338
+ exports.sendTransfer = sendTransfer;
339
+ const buildTransferIntentInstruction = async (program, options, symbol) => {
340
+ const [nonce, { decimals }] = await Promise.all([
341
+ getNonce(program, options.walletPublicKey),
342
+ (0, spl_token_1.getMint)(options.adapter.connection, options.mint),
343
+ ]);
344
+ const message = new TextEncoder().encode([
345
+ TRANSFER_MESSAGE_HEADER,
346
+ serializeKV({
347
+ version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
348
+ chain_id: options.adapter.chainId,
349
+ token: symbol ?? options.mint.toBase58(),
350
+ amount: amountToString(options.amount, decimals),
351
+ recipient: options.recipient.toBase58(),
352
+ nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
353
+ }),
354
+ ].join("\n"));
355
+ const intentSignature = await options.signMessage(message);
356
+ return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
357
+ publicKey: options.walletPublicKey.toBytes(),
358
+ signature: intentSignature,
359
+ message: message,
360
+ });
361
+ };
362
+ const getNonce = async (program, walletPublicKey) => {
363
+ const [noncePda] = await (0, kit_1.getProgramDerivedAddress)({
364
+ programAddress: (0, compat_1.fromLegacyPublicKey)(program.programId),
365
+ seeds: [Buffer.from("nonce"), walletPublicKey.toBuffer()],
366
+ });
367
+ return program.account.nonce.fetchNullable(noncePda);
368
+ };
package/esm/adapter.d.ts CHANGED
@@ -6,7 +6,7 @@ export type SessionAdapter = {
6
6
  connection: Connection;
7
7
  payer: PublicKey;
8
8
  domain: string;
9
- sendTransaction: (sessionKey: CryptoKeyPair, instructions: (TransactionInstruction | IInstruction)[] | VersionedTransaction | Transaction) => Promise<TransactionResult>;
9
+ sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | IInstruction)[] | VersionedTransaction | Transaction) => Promise<TransactionResult>;
10
10
  };
11
11
  export declare enum TransactionResultType {
12
12
  Success = 0,
@@ -31,7 +31,7 @@ export declare const createSolanaWalletAdapter: (options: {
31
31
  } & ({
32
32
  paymaster?: string | URL | undefined;
33
33
  } | {
34
- sendToPaymaster: (transaction: Transaction) => Promise<string>;
34
+ sendToPaymaster: (transaction: Transaction) => Promise<TransactionResult>;
35
35
  sponsor: PublicKey;
36
36
  })) => Promise<SessionAdapter>;
37
37
  export {};
package/esm/adapter.js CHANGED
@@ -26,7 +26,8 @@ const TransactionResult = {
26
26
  };
27
27
  export const createSolanaWalletAdapter = async (options) => {
28
28
  const addressLookupTables = await getAddressLookupTables(options.connection, options.addressLookupTableAddress);
29
- const sponsor = await getSponsor(options);
29
+ const domain = getDomain(options.domain);
30
+ const sponsor = await getSponsor(options, domain);
30
31
  return {
31
32
  connection: options.connection,
32
33
  payer: sponsor,
@@ -35,45 +36,70 @@ export const createSolanaWalletAdapter = async (options) => {
35
36
  sendTransaction: async (sessionKey, instructions) => {
36
37
  const rpc = createSolanaRpc(options.connection.rpcEndpoint);
37
38
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
38
- const sessionKeySigner = await createSignerFromKeyPair(sessionKey);
39
- const transaction = Array.isArray(instructions)
40
- ? await partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(fromLegacyPublicKey(sponsor), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions.map((instruction) => instruction instanceof TransactionInstruction
41
- ? fromLegacyTransactionInstruction(instruction)
42
- : instruction), tx), (tx) => compressTransactionMessageUsingAddressLookupTables(tx, Object.fromEntries(addressLookupTables?.map((table) => [
43
- fromLegacyPublicKey(table.key),
44
- table.state.addresses.map((address) => fromLegacyPublicKey(address)),
45
- ]) ?? [])), (tx) => addSignersToTransactionMessage([sessionKeySigner], tx)))
46
- : await partiallySignTransaction([sessionKey], instructions instanceof VersionedTransaction
47
- ? fromVersionedTransaction(instructions)
48
- : instructions);
49
- const signature = await sendToPaymaster(options, transaction);
50
- const lastValidBlockHeight = await rpc.getSlot().send();
51
- const confirmationResult = await options.connection.confirmTransaction({
52
- signature,
53
- blockhash: latestBlockhash.blockhash.toString(),
54
- lastValidBlockHeight: Number(lastValidBlockHeight),
55
- });
56
- return confirmationResult.value.err === null
57
- ? TransactionResult.Success(signature)
58
- : TransactionResult.Failed(signature, confirmationResult.value.err);
39
+ return await sendToPaymaster(options, await buildTransaction(latestBlockhash, sessionKey, sponsor, instructions, addressLookupTables), domain);
59
40
  },
60
41
  };
61
42
  };
62
- const getSponsor = async (options) => {
43
+ const buildTransaction = async (latestBlockhash, sessionKey, sponsor, instructions, addressLookupTables) => {
44
+ const sessionKeySigner = sessionKey
45
+ ? await createSignerFromKeyPair(sessionKey)
46
+ : undefined;
47
+ if (Array.isArray(instructions)) {
48
+ return partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(fromLegacyPublicKey(sponsor), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions.map((instruction) => instruction instanceof TransactionInstruction
49
+ ? fromLegacyTransactionInstruction(instruction)
50
+ : instruction), tx), (tx) => compressTransactionMessageUsingAddressLookupTables(tx, Object.fromEntries(addressLookupTables?.map((table) => [
51
+ fromLegacyPublicKey(table.key),
52
+ table.state.addresses.map((address) => fromLegacyPublicKey(address)),
53
+ ]) ?? [])), (tx) => sessionKeySigner === undefined
54
+ ? tx
55
+ : addSignersToTransactionMessage([sessionKeySigner], tx)));
56
+ }
57
+ else {
58
+ const tx = instructions instanceof VersionedTransaction
59
+ ? fromVersionedTransaction(instructions)
60
+ : instructions;
61
+ return sessionKey === undefined
62
+ ? tx
63
+ : partiallySignTransaction([sessionKey], tx);
64
+ }
65
+ };
66
+ const getSponsor = async (options, domain) => {
63
67
  if ("sponsor" in options) {
64
68
  return options.sponsor;
65
69
  }
66
70
  else {
67
- const response = await fetch(new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER));
71
+ const url = new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER);
72
+ url.searchParams.set("domain", domain);
73
+ const response = await fetch(url);
68
74
  return new PublicKey(z.string().parse(await response.text()));
69
75
  }
70
76
  };
71
- const sendToPaymaster = async (options, transaction) => {
77
+ const sponsorAndSendResponseSchema = z
78
+ .discriminatedUnion("type", [
79
+ z.object({
80
+ type: z.literal("success"),
81
+ signature: z.string(),
82
+ }),
83
+ z.object({
84
+ type: z.literal("failed"),
85
+ signature: z.string(),
86
+ error: z.object({}),
87
+ }),
88
+ ])
89
+ .transform((data) => {
90
+ return data.type === "success"
91
+ ? TransactionResult.Success(data.signature)
92
+ : TransactionResult.Failed(data.signature, data.error);
93
+ });
94
+ const sendToPaymaster = async (options, transaction, domain) => {
72
95
  if ("sendToPaymaster" in options) {
73
96
  return options.sendToPaymaster(transaction);
74
97
  }
75
98
  else {
76
- const response = await fetch(new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER), {
99
+ const url = new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER);
100
+ url.searchParams.set("confirm", "true");
101
+ url.searchParams.set("domain", domain);
102
+ const response = await fetch(url, {
77
103
  method: "POST",
78
104
  headers: {
79
105
  "Content-Type": "application/json",
@@ -83,7 +109,7 @@ const sendToPaymaster = async (options, transaction) => {
83
109
  }),
84
110
  });
85
111
  if (response.status === 200) {
86
- return response.text();
112
+ return sponsorAndSendResponseSchema.parse(await response.json());
87
113
  }
88
114
  else {
89
115
  throw new PaymasterResponseError(response.status, await response.text());
package/esm/index.d.ts CHANGED
@@ -290,3 +290,12 @@ export type Session = {
290
290
  sendTransaction: (instructions: Parameters<SessionAdapter["sendTransaction"]>[1]) => Promise<TransactionResult>;
291
291
  sessionInfo: z.infer<typeof sessionInfoSchema>;
292
292
  };
293
+ type SendTransferOptions = {
294
+ adapter: SessionAdapter;
295
+ walletPublicKey: PublicKey;
296
+ signMessage: (message: Uint8Array) => Promise<Uint8Array>;
297
+ mint: PublicKey;
298
+ amount: bigint;
299
+ recipient: PublicKey;
300
+ };
301
+ export declare const sendTransfer: (options: SendTransferOptions) => Promise<TransactionResult>;
package/esm/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { AnchorProvider, BorshAccountsCoder } from "@coral-xyz/anchor";
2
- import { DomainRegistryIdl, SessionManagerProgram, SessionManagerIdl, } from "@fogo/sessions-idls";
2
+ import { DomainRegistryIdl, SessionManagerProgram, SessionManagerIdl, IntentTransferProgram, } from "@fogo/sessions-idls";
3
3
  import { findMetadataPda, safeFetchMetadata, } from "@metaplex-foundation/mpl-token-metadata";
4
4
  import { publicKey as metaplexPublicKey } from "@metaplex-foundation/umi";
5
5
  import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
6
6
  import { sha256 } from "@noble/hashes/sha2";
7
- import { generateKeyPair, getAddressFromPublicKey } from "@solana/kit";
7
+ import { fromLegacyPublicKey } from "@solana/compat";
8
+ import { generateKeyPair, getAddressFromPublicKey, getProgramDerivedAddress, } from "@solana/kit";
8
9
  import { createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddressSync, getMint, } from "@solana/spl-token";
9
10
  import { Ed25519Program, PublicKey } from "@solana/web3.js";
10
11
  import BN from "bn.js";
@@ -18,6 +19,8 @@ const UNLIMITED_TOKEN_PERMISSIONS_VALUE = "this app may spend any amount of any
18
19
  const TOKENLESS_PERMISSIONS_VALUE = "this app may not spend any tokens";
19
20
  const CURRENT_MAJOR = "0";
20
21
  const CURRENT_MINOR = "1";
22
+ const CURRENT_INTENT_TRANSFER_MAJOR = "0";
23
+ const CURRENT_INTENT_TRANSFER_MINOR = "1";
21
24
  export const establishSession = async (options) => {
22
25
  const sessionKey = await generateKeyPair();
23
26
  if (options.unlimited) {
@@ -143,10 +146,11 @@ export var AuthorizedTokens;
143
146
  AuthorizedTokens[AuthorizedTokens["All"] = 0] = "All";
144
147
  AuthorizedTokens[AuthorizedTokens["Specific"] = 1] = "Specific";
145
148
  })(AuthorizedTokens || (AuthorizedTokens = {}));
146
- const SymbolOrMintType = {
147
- Symbol: "Symbol",
148
- Mint: "Mint",
149
- };
149
+ var SymbolOrMintType;
150
+ (function (SymbolOrMintType) {
151
+ SymbolOrMintType[SymbolOrMintType["Symbol"] = 0] = "Symbol";
152
+ SymbolOrMintType[SymbolOrMintType["Mint"] = 1] = "Mint";
153
+ })(SymbolOrMintType || (SymbolOrMintType = {}));
150
154
  const SymbolOrMint = {
151
155
  Symbol: (symbol) => ({
152
156
  type: SymbolOrMintType.Symbol,
@@ -290,3 +294,61 @@ const EstablishSessionResult = {
290
294
  error,
291
295
  }),
292
296
  };
297
+ const TRANSFER_MESSAGE_HEADER = `Fogo Transfer:
298
+ Signing this intent will transfer the tokens as described below.
299
+ `;
300
+ export const sendTransfer = async (options) => {
301
+ const sourceAta = getAssociatedTokenAddressSync(options.mint, options.walletPublicKey);
302
+ const destinationAta = getAssociatedTokenAddressSync(options.mint, options.recipient);
303
+ const program = new IntentTransferProgram(new AnchorProvider(options.adapter.connection, {}, {}));
304
+ const umi = createUmi(options.adapter.connection.rpcEndpoint);
305
+ const metaplexMint = metaplexPublicKey(options.mint.toBase58());
306
+ const metadataAddress = findMetadataPda(umi, { mint: metaplexMint })[0];
307
+ const metadata = await safeFetchMetadata(umi, metadataAddress);
308
+ const symbol = metadata?.symbol ?? undefined;
309
+ return options.adapter.sendTransaction(undefined, [
310
+ createAssociatedTokenAccountIdempotentInstruction(options.adapter.payer, destinationAta, options.recipient, options.mint),
311
+ await buildTransferIntentInstruction(program, options, symbol),
312
+ await program.methods
313
+ .sendTokens()
314
+ .accounts({
315
+ destination: destinationAta,
316
+ mint: options.mint,
317
+ source: sourceAta,
318
+ sponsor: options.adapter.payer,
319
+ // eslint-disable-next-line unicorn/no-null
320
+ metadata: symbol === undefined ? null : new PublicKey(metadataAddress),
321
+ })
322
+ .instruction(),
323
+ ]);
324
+ };
325
+ const buildTransferIntentInstruction = async (program, options, symbol) => {
326
+ const [nonce, { decimals }] = await Promise.all([
327
+ getNonce(program, options.walletPublicKey),
328
+ getMint(options.adapter.connection, options.mint),
329
+ ]);
330
+ const message = new TextEncoder().encode([
331
+ TRANSFER_MESSAGE_HEADER,
332
+ serializeKV({
333
+ version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
334
+ chain_id: options.adapter.chainId,
335
+ token: symbol ?? options.mint.toBase58(),
336
+ amount: amountToString(options.amount, decimals),
337
+ recipient: options.recipient.toBase58(),
338
+ nonce: nonce === null ? "1" : nonce.nonce.add(new BN(1)).toString(),
339
+ }),
340
+ ].join("\n"));
341
+ const intentSignature = await options.signMessage(message);
342
+ return Ed25519Program.createInstructionWithPublicKey({
343
+ publicKey: options.walletPublicKey.toBytes(),
344
+ signature: intentSignature,
345
+ message: message,
346
+ });
347
+ };
348
+ const getNonce = async (program, walletPublicKey) => {
349
+ const [noncePda] = await getProgramDerivedAddress({
350
+ programAddress: fromLegacyPublicKey(program.programId),
351
+ seeds: [Buffer.from("nonce"), walletPublicKey.toBuffer()],
352
+ });
353
+ return program.account.nonce.fetchNullable(noncePda);
354
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fogo/sessions-sdk",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "A set of utilities for integrating with Fogo sessions",
5
5
  "keywords": [
6
6
  "fogo",
@@ -48,6 +48,6 @@
48
48
  "bn.js": "^5.1.2",
49
49
  "bs58": "^6.0.0",
50
50
  "zod": "^3.25.62",
51
- "@fogo/sessions-idls": "^0.0.3"
51
+ "@fogo/sessions-idls": "^0.0.4"
52
52
  }
53
53
  }