@fogo/sessions-sdk 0.0.17 → 0.0.19

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.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.createSessionContext = void 0;
6
+ exports.verifyLogInToken = exports.createLogInToken = exports.bridgeIn = exports.bridgeOut = exports.sendTransfer = exports.SessionResultType = exports.getDomainRecordAddress = exports.AuthorizedTokens = exports.AuthorizedProgramsType = exports.getSessionAccount = exports.reestablishSession = exports.revokeSession = exports.replaceSession = exports.establishSession = exports.createSessionConnection = exports.TransactionResultType = exports.Network = exports.createSessionContext = 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");
@@ -14,14 +14,23 @@ const compat_1 = require("@solana/compat");
14
14
  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
+ const sdk_1 = require("@wormhole-foundation/sdk");
18
+ const solana_1 = __importDefault(require("@wormhole-foundation/sdk/solana"));
19
+ const sdk_base_1 = require("@wormhole-foundation/sdk-base");
20
+ const sdk_route_ntt_1 = require("@wormhole-foundation/sdk-route-ntt");
21
+ const sdk_solana_core_1 = require("@wormhole-foundation/sdk-solana-core");
22
+ const sdk_solana_ntt_1 = require("@wormhole-foundation/sdk-solana-ntt");
17
23
  const bn_js_1 = __importDefault(require("bn.js"));
18
24
  const bs58_1 = __importDefault(require("bs58"));
19
25
  const zod_1 = require("zod");
20
- const context_js_1 = require("./context.js");
26
+ const connection_js_1 = require("./connection.js");
21
27
  const crypto_js_1 = require("./crypto.js");
22
- var context_js_2 = require("./context.js");
23
- Object.defineProperty(exports, "createSessionContext", { enumerable: true, get: function () { return context_js_2.createSessionContext; } });
24
- Object.defineProperty(exports, "TransactionResultType", { enumerable: true, get: function () { return context_js_2.TransactionResultType; } });
28
+ var context_js_1 = require("./context.js");
29
+ Object.defineProperty(exports, "createSessionContext", { enumerable: true, get: function () { return context_js_1.createSessionContext; } });
30
+ var connection_js_2 = require("./connection.js");
31
+ Object.defineProperty(exports, "Network", { enumerable: true, get: function () { return connection_js_2.Network; } });
32
+ Object.defineProperty(exports, "TransactionResultType", { enumerable: true, get: function () { return connection_js_2.TransactionResultType; } });
33
+ Object.defineProperty(exports, "createSessionConnection", { enumerable: true, get: function () { return connection_js_2.createSessionConnection; } });
25
34
  const MESSAGE_HEADER = `Fogo Sessions:
26
35
  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.
27
36
  `;
@@ -31,6 +40,12 @@ const CURRENT_MAJOR = "0";
31
40
  const CURRENT_MINOR = "3";
32
41
  const CURRENT_INTENT_TRANSFER_MAJOR = "0";
33
42
  const CURRENT_INTENT_TRANSFER_MINOR = "1";
43
+ const CURRENT_BRIDGE_OUT_MAJOR = "0";
44
+ const CURRENT_BRIDGE_OUT_MINOR = "1";
45
+ const SESSION_ESTABLISHMENT_LOOKUP_TABLE_ADDRESS = {
46
+ [connection_js_1.Network.Testnet]: "B8cUjJMqaWWTNNSTXBmeptjWswwCH1gTSCRYv4nu7kJW",
47
+ [connection_js_1.Network.Mainnet]: undefined,
48
+ };
34
49
  const establishSession = async (options) => {
35
50
  const sessionKey = options.createUnsafeExtractableSessionKey
36
51
  ? await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])
@@ -39,7 +54,7 @@ const establishSession = async (options) => {
39
54
  return sendSessionEstablishTransaction(options, sessionKey, await Promise.all([
40
55
  buildIntentInstruction(options, sessionKey),
41
56
  buildStartSessionInstruction(options, sessionKey),
42
- ]));
57
+ ]), options.sessionEstablishmentLookupTable);
43
58
  }
44
59
  else {
45
60
  const filteredLimits = new Map(options.limits?.entries().filter(([, amount]) => amount > 0n));
@@ -54,20 +69,23 @@ const establishSession = async (options) => {
54
69
  ...buildCreateAssociatedTokenAccountInstructions(options, tokenInfo),
55
70
  intentInstruction,
56
71
  startSessionInstruction,
57
- ]);
72
+ ], options.sessionEstablishmentLookupTable);
58
73
  }
59
74
  };
60
75
  exports.establishSession = establishSession;
61
- const sendSessionEstablishTransaction = async (options, sessionKey, instructions) => {
62
- const result = await options.context.sendTransaction(sessionKey, instructions);
76
+ const sendSessionEstablishTransaction = async (options, sessionKey, instructions, sessionEstablishmentLookupTable) => {
77
+ const result = await options.context.sendTransaction(sessionKey, instructions, {
78
+ addressLookupTable: sessionEstablishmentLookupTable ??
79
+ SESSION_ESTABLISHMENT_LOOKUP_TABLE_ADDRESS[options.context.network],
80
+ });
63
81
  switch (result.type) {
64
- case context_js_1.TransactionResultType.Success: {
82
+ case connection_js_1.TransactionResultType.Success: {
65
83
  const session = await createSession(options.context, options.walletPublicKey, sessionKey);
66
84
  return session
67
85
  ? EstablishSessionResult.Success(result.signature, session)
68
86
  : EstablishSessionResult.Failed(result.signature, new Error("Transaction succeeded, but the session was not created"));
69
87
  }
70
- case context_js_1.TransactionResultType.Failed: {
88
+ case connection_js_1.TransactionResultType.Failed: {
71
89
  return EstablishSessionResult.Failed(result.signature, result.error);
72
90
  }
73
91
  }
@@ -423,6 +441,13 @@ const getDomainRecordAddress = (domain) => {
423
441
  return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("domain-record"), hash], new web3_js_1.PublicKey(sessions_idls_1.DomainRegistryIdl.address))[0];
424
442
  };
425
443
  exports.getDomainRecordAddress = getDomainRecordAddress;
444
+ const BRIDGING_ADDRESS_LOOKUP_TABLE = {
445
+ [connection_js_1.Network.Testnet]: {
446
+ // USDC
447
+ ELNbJ1RtERV2fjtuZjbTscDekWhVzkQ1LjmiPsxp5uND: "4FCi6LptexBdZtaePsoCMeb1XpCijxnWu96g5LsSb6WP",
448
+ },
449
+ [connection_js_1.Network.Mainnet]: undefined,
450
+ };
426
451
  const buildStartSessionInstruction = async (options, sessionKey, tokens) => {
427
452
  const instruction = new sessions_idls_1.SessionManagerProgram(new anchor_1.AnchorProvider(options.context.connection, {}, {})).methods
428
453
  .startSession()
@@ -505,7 +530,7 @@ const sendTransfer = async (options) => {
505
530
  exports.sendTransfer = sendTransfer;
506
531
  const buildTransferIntentInstruction = async (program, options, symbol) => {
507
532
  const [nonce, { decimals }] = await Promise.all([
508
- getNonce(program, options.walletPublicKey),
533
+ getNonce(program, options.walletPublicKey, NonceType.Transfer),
509
534
  (0, spl_token_1.getMint)(options.context.connection, options.mint),
510
535
  ]);
511
536
  const message = new TextEncoder().encode([
@@ -526,13 +551,208 @@ const buildTransferIntentInstruction = async (program, options, symbol) => {
526
551
  message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
527
552
  });
528
553
  };
529
- const getNonce = async (program, walletPublicKey) => {
554
+ const BRIDGE_OUT_MESSAGE_HEADER = `Fogo Bridge Transfer:
555
+ Signing this intent will bridge out the tokens as described below.
556
+ `;
557
+ const bridgeOut = async (options) => {
558
+ const { wh, route, transferRequest, transferParams, decimals } = await buildWormholeTransfer(options, options.context.connection);
559
+ // @ts-expect-error the wormhole client types are incorrect and do not
560
+ // properly represent the runtime representation.
561
+ const quote = await route.fetchExecutorQuote(transferRequest, transferParams);
562
+ const program = new sessions_idls_1.IntentTransferProgram(new anchor_1.AnchorProvider(options.context.connection, {}, {}));
563
+ const umi = (0, umi_bundle_defaults_1.createUmi)(options.context.connection.rpcEndpoint);
564
+ const metaplexMint = (0, umi_1.publicKey)(options.fromToken.mint.toBase58());
565
+ const metadataAddress = (0, mpl_token_metadata_1.findMetadataPda)(umi, { mint: metaplexMint })[0];
566
+ const outboxItem = web3_js_1.Keypair.generate();
567
+ const [metadata, nttPdas] = await Promise.all([
568
+ (0, mpl_token_metadata_1.safeFetchMetadata)(umi, metadataAddress),
569
+ getNttPdas(options, wh, program, outboxItem.publicKey, new web3_js_1.PublicKey(quote.payeeAddress)),
570
+ ]);
571
+ const bridgeInstruction = await program.methods
572
+ .bridgeNttTokens({
573
+ execAmount: new bn_js_1.default(quote.estimatedCost.toString()),
574
+ relayInstructions: Buffer.from(quote.relayInstructions),
575
+ signedQuoteBytes: Buffer.from(quote.signedQuote),
576
+ })
577
+ .accounts({
578
+ sponsor: options.context.payer,
579
+ mint: options.fromToken.mint,
580
+ metadata:
581
+ // eslint-disable-next-line unicorn/no-null
582
+ metadata?.symbol === undefined ? null : new web3_js_1.PublicKey(metadataAddress),
583
+ source: (0, spl_token_1.getAssociatedTokenAddressSync)(options.fromToken.mint, options.walletPublicKey),
584
+ ntt: nttPdas,
585
+ })
586
+ .instruction();
587
+ return options.context.sendTransaction(options.sessionKey, [
588
+ await buildBridgeOutIntent(program, options, decimals, metadata?.symbol),
589
+ bridgeInstruction,
590
+ ], {
591
+ extraSigners: [outboxItem],
592
+ addressLookupTable: BRIDGING_ADDRESS_LOOKUP_TABLE[options.context.network]?.[options.fromToken.mint.toBase58()],
593
+ });
594
+ };
595
+ exports.bridgeOut = bridgeOut;
596
+ // Here we use the Wormhole SDKs to produce the wormhole pdas that are needed
597
+ // for the bridge out transaction. Currently this is using wormhole SDK apis
598
+ // that are _technically_ public but it seems likely these are not considered to
599
+ // be truly public. It might be better to extract the pdas by using the sdk to
600
+ // generate (but not send) a transaction, and taking the pdas from that. That
601
+ // may be something to revisit in the future if we find that the wormhole sdk
602
+ // upgrades in ways that break these calls.
603
+ const getNttPdas = async (options, wh, program, outboxItemPublicKey, quotePayeeAddress) => {
604
+ const pdas = sdk_solana_ntt_1.NTT.pdas(options.fromToken.manager);
605
+ const solana = wh.getChain("Solana");
606
+ const coreBridgeContract = sdk_base_1.contracts.coreBridge.get(wh.network, "Fogo");
607
+ if (coreBridgeContract === undefined) {
608
+ throw new Error("Core bridge contract address not returned by wormhole!");
609
+ }
610
+ const transceiverPdas = sdk_solana_ntt_1.NTT.transceiverPdas(options.fromToken.manager);
611
+ const [intentTransferSetterPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("intent_transfer")], program.programId);
612
+ const wormholePdas = sdk_solana_core_1.utils.getWormholeDerivedAccounts(options.fromToken.manager, coreBridgeContract);
613
+ return {
614
+ emitter: transceiverPdas.emitterAccount(),
615
+ nttConfig: pdas.configAccount(),
616
+ nttCustody: await sdk_solana_ntt_1.NTT.custodyAccountAddress(pdas, options.fromToken.mint),
617
+ nttInboxRateLimit: pdas.inboxRateLimitAccount(solana.chain),
618
+ nttManager: options.fromToken.manager,
619
+ nttOutboxItem: outboxItemPublicKey,
620
+ nttOutboxRateLimit: pdas.outboxRateLimitAccount(),
621
+ nttPeer: pdas.peerAccount(solana.chain),
622
+ nttSessionAuthority: pdas.sessionAuthority(intentTransferSetterPda, sdk_solana_ntt_1.NTT.transferArgs(options.amount, sdk_1.Wormhole.chainAddress("Solana", options.walletPublicKey.toBase58()), false)),
623
+ nttTokenAuthority: pdas.tokenAuthority(),
624
+ payeeNttWithExecutor: quotePayeeAddress,
625
+ transceiver: options.fromToken.transceiver,
626
+ wormholeBridge: wormholePdas.wormholeBridge,
627
+ wormholeFeeCollector: wormholePdas.wormholeFeeCollector,
628
+ wormholeMessage: transceiverPdas.wormholeMessageAccount(outboxItemPublicKey),
629
+ wormholeSequence: wormholePdas.wormholeSequence,
630
+ };
631
+ };
632
+ const buildBridgeOutIntent = async (program, options, decimals, symbol) => {
633
+ const nonce = await getNonce(program, options.walletPublicKey, NonceType.Bridge);
634
+ const message = new TextEncoder().encode([
635
+ BRIDGE_OUT_MESSAGE_HEADER,
636
+ serializeKV({
637
+ version: `${CURRENT_BRIDGE_OUT_MAJOR}.${CURRENT_BRIDGE_OUT_MINOR}`,
638
+ from_chain_id: options.context.chainId,
639
+ to_chain_id: "solana",
640
+ token: symbol ?? options.fromToken.mint.toBase58(),
641
+ amount: amountToString(options.amount, decimals),
642
+ recipient_address: options.walletPublicKey.toBase58(),
643
+ nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
644
+ }),
645
+ ].join("\n"));
646
+ const intentSignature = (0, kit_1.signatureBytes)(await options.solanaWallet.signMessage(message));
647
+ return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
648
+ publicKey: options.walletPublicKey.toBytes(),
649
+ signature: intentSignature,
650
+ message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
651
+ });
652
+ };
653
+ const bridgeIn = async (options) => {
654
+ const solanaConnection = await options.context.getSolanaConnection();
655
+ const { route, transferRequest, transferParams } = await buildWormholeTransfer(options, solanaConnection);
656
+ // @ts-expect-error the wormhole client types are incorrect and do not
657
+ // properly represent the runtime representation.
658
+ const quote = await route.quote(transferRequest, transferParams);
659
+ if (quote.success) {
660
+ return await sdk_1.routes.checkAndCompleteTransfer(route, await route.initiate(transferRequest, {
661
+ address: () => options.walletPublicKey.toBase58(),
662
+ chain: () => "Solana",
663
+ signAndSend: (transactions) => Promise.all(transactions.map(({ transaction }) => options.solanaWallet.sendTransaction(
664
+ // Hooray for Wormhole's incomplete typing eh?
665
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
666
+ transaction.transaction, solanaConnection,
667
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
668
+ { signers: transaction.signers, skipPreflight: true }))),
669
+ }, quote, sdk_1.Wormhole.chainAddress("Fogo", options.walletPublicKey.toBase58())));
670
+ }
671
+ else {
672
+ throw quote.error;
673
+ }
674
+ };
675
+ exports.bridgeIn = bridgeIn;
676
+ const buildWormholeTransfer = async (options, connection) => {
677
+ const [wh, { decimals }] = await Promise.all([
678
+ (0, sdk_1.wormhole)(NETWORK_TO_WORMHOLE_NETWORK[options.context.network], [solana_1.default]),
679
+ (0, spl_token_1.getMint)(connection, options.fromToken.mint),
680
+ ]);
681
+ const Route = (0, sdk_route_ntt_1.nttExecutorRoute)({
682
+ ntt: {
683
+ tokens: {
684
+ USDC: [
685
+ {
686
+ chain: options.fromToken.chain,
687
+ manager: options.fromToken.manager.toBase58(),
688
+ token: options.fromToken.mint.toBase58(),
689
+ transceiver: [
690
+ {
691
+ address: options.fromToken.transceiver.toBase58(),
692
+ type: "wormhole",
693
+ },
694
+ ],
695
+ },
696
+ {
697
+ chain: options.toToken.chain,
698
+ manager: options.toToken.manager.toBase58(),
699
+ token: options.toToken.mint.toBase58(),
700
+ transceiver: [
701
+ {
702
+ address: options.toToken.transceiver.toBase58(),
703
+ type: "wormhole",
704
+ },
705
+ ],
706
+ },
707
+ ],
708
+ },
709
+ },
710
+ });
711
+ const route = new Route(wh);
712
+ const transferRequest = await sdk_1.routes.RouteTransferRequest.create(wh, {
713
+ source: sdk_1.Wormhole.tokenId(options.fromToken.chain, options.fromToken.mint.toBase58()),
714
+ destination: sdk_1.Wormhole.tokenId(options.toToken.chain, options.toToken.mint.toBase58()),
715
+ });
716
+ const validated = await route.validate(transferRequest, {
717
+ amount: amountToString(options.amount, decimals),
718
+ options: route.getDefaultOptions(),
719
+ });
720
+ if (validated.valid) {
721
+ return {
722
+ wh,
723
+ route,
724
+ transferRequest,
725
+ transferParams: validated.params,
726
+ decimals,
727
+ };
728
+ }
729
+ else {
730
+ throw validated.error;
731
+ }
732
+ };
733
+ const NETWORK_TO_WORMHOLE_NETWORK = {
734
+ [connection_js_1.Network.Mainnet]: "Mainnet",
735
+ [connection_js_1.Network.Testnet]: "Testnet",
736
+ };
737
+ const getNonce = async (program, walletPublicKey, nonceType) => {
530
738
  const [noncePda] = await (0, kit_1.getProgramDerivedAddress)({
531
739
  programAddress: (0, compat_1.fromLegacyPublicKey)(program.programId),
532
- seeds: [Buffer.from("nonce"), walletPublicKey.toBuffer()],
740
+ seeds: [
741
+ Buffer.from(NONCE_TYPE_TO_SEED[nonceType]),
742
+ walletPublicKey.toBuffer(),
743
+ ],
533
744
  });
534
745
  return program.account.nonce.fetchNullable(noncePda);
535
746
  };
747
+ var NonceType;
748
+ (function (NonceType) {
749
+ NonceType[NonceType["Transfer"] = 0] = "Transfer";
750
+ NonceType[NonceType["Bridge"] = 1] = "Bridge";
751
+ })(NonceType || (NonceType = {}));
752
+ const NONCE_TYPE_TO_SEED = {
753
+ [NonceType.Transfer]: "nonce",
754
+ [NonceType.Bridge]: "bridge_ntt_nonce",
755
+ };
536
756
  const loginTokenPayloadSchema = zod_1.z.object({
537
757
  iat: zod_1.z.number(),
538
758
  sessionPublicKey: zod_1.z.string(),
@@ -0,0 +1,47 @@
1
+ import type { Transaction, Instruction, TransactionWithLifetime } from "@solana/kit";
2
+ import type { TransactionError } from "@solana/web3.js";
3
+ import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey } from "@solana/web3.js";
4
+ export declare enum Network {
5
+ Testnet = 0,
6
+ Mainnet = 1
7
+ }
8
+ export declare enum TransactionResultType {
9
+ Success = 0,
10
+ Failed = 1
11
+ }
12
+ declare const TransactionResult: {
13
+ Success: (signature: string) => {
14
+ type: TransactionResultType.Success;
15
+ signature: string;
16
+ };
17
+ Failed: (signature: string, error: TransactionError) => {
18
+ type: TransactionResultType.Failed;
19
+ signature: string;
20
+ error: TransactionError;
21
+ };
22
+ };
23
+ export type TransactionResult = ReturnType<(typeof TransactionResult)[keyof typeof TransactionResult]>;
24
+ export declare const createSessionConnection: (options: {
25
+ network: Network;
26
+ rpc?: string | URL | undefined;
27
+ } & ({
28
+ paymaster?: string | URL | undefined;
29
+ sendToPaymaster?: undefined;
30
+ sponsor?: undefined;
31
+ } | {
32
+ paymaster?: undefined;
33
+ sendToPaymaster: (transaction: Transaction) => Promise<TransactionResult>;
34
+ sponsor: PublicKey;
35
+ })) => {
36
+ rpc: import("@solana/kit").Rpc<import("@solana/kit").RequestAirdropApi & import("@solana/kit").GetAccountInfoApi & import("@solana/kit").GetBalanceApi & import("@solana/kit").GetBlockApi & import("@solana/kit").GetBlockCommitmentApi & import("@solana/kit").GetBlockHeightApi & import("@solana/kit").GetBlockProductionApi & import("@solana/kit").GetBlocksApi & import("@solana/kit").GetBlocksWithLimitApi & import("@solana/kit").GetBlockTimeApi & import("@solana/kit").GetClusterNodesApi & import("@solana/kit").GetEpochInfoApi & import("@solana/kit").GetEpochScheduleApi & import("@solana/kit").GetFeeForMessageApi & import("@solana/kit").GetFirstAvailableBlockApi & import("@solana/kit").GetGenesisHashApi & import("@solana/kit").GetHealthApi & import("@solana/kit").GetHighestSnapshotSlotApi & import("@solana/kit").GetIdentityApi & import("@solana/kit").GetInflationGovernorApi & import("@solana/kit").GetInflationRateApi & import("@solana/kit").GetInflationRewardApi & import("@solana/kit").GetLargestAccountsApi & import("@solana/kit").GetLatestBlockhashApi & import("@solana/kit").GetLeaderScheduleApi & import("@solana/kit").GetMaxRetransmitSlotApi & import("@solana/kit").GetMaxShredInsertSlotApi & import("@solana/kit").GetMinimumBalanceForRentExemptionApi & import("@solana/kit").GetMultipleAccountsApi & import("@solana/kit").GetProgramAccountsApi & import("@solana/kit").GetRecentPerformanceSamplesApi & import("@solana/kit").GetRecentPrioritizationFeesApi & import("@solana/kit").GetSignaturesForAddressApi & import("@solana/kit").GetSignatureStatusesApi & import("@solana/kit").GetSlotApi & import("@solana/kit").GetSlotLeaderApi & import("@solana/kit").GetSlotLeadersApi & import("@solana/kit").GetStakeMinimumDelegationApi & import("@solana/kit").GetSupplyApi & import("@solana/kit").GetTokenAccountBalanceApi & import("@solana/kit").GetTokenAccountsByDelegateApi & import("@solana/kit").GetTokenAccountsByOwnerApi & import("@solana/kit").GetTokenLargestAccountsApi & import("@solana/kit").GetTokenSupplyApi & import("@solana/kit").GetTransactionApi & import("@solana/kit").GetTransactionCountApi & import("@solana/kit").GetVersionApi & import("@solana/kit").GetVoteAccountsApi & import("@solana/kit").IsBlockhashValidApi & import("@solana/kit").MinimumLedgerSlotApi & import("@solana/kit").SendTransactionApi & import("@solana/kit").SimulateTransactionApi>;
37
+ connection: Web3Connection;
38
+ network: Network;
39
+ getSolanaConnection: () => Promise<Web3Connection>;
40
+ sendToPaymaster: (domain: string, sponsor: PublicKey, sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | Instruction)[] | VersionedTransaction | (Transaction & TransactionWithLifetime), extraConfig?: {
41
+ addressLookupTable?: string | undefined;
42
+ extraSigners?: (CryptoKeyPair | Keypair)[] | undefined;
43
+ }) => Promise<TransactionResult>;
44
+ getSponsor: (domain: string) => Promise<PublicKey>;
45
+ };
46
+ export type Connection = ReturnType<typeof createSessionConnection>;
47
+ export {};
@@ -0,0 +1,187 @@
1
+ import { fromLegacyKeypair, fromLegacyPublicKey, fromLegacyTransactionInstruction, fromVersionedTransaction, } from "@solana/compat";
2
+ import { createSolanaRpc, getBase64EncodedWireTransaction, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, partiallySignTransactionMessageWithSigners, pipe, addSignersToTransactionMessage, compressTransactionMessageUsingAddressLookupTables, createSignerFromKeyPair, partiallySignTransaction, } from "@solana/kit";
3
+ import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey, } from "@solana/web3.js";
4
+ import { z } from "zod";
5
+ export var Network;
6
+ (function (Network) {
7
+ Network[Network["Testnet"] = 0] = "Testnet";
8
+ Network[Network["Mainnet"] = 1] = "Mainnet";
9
+ })(Network || (Network = {}));
10
+ const DEFAULT_RPC = {
11
+ [Network.Testnet]: "https://testnet.fogo.io",
12
+ [Network.Mainnet]: "https://mainnet.fogo.io",
13
+ };
14
+ const DEFAULT_PAYMASTER = {
15
+ [Network.Testnet]: "https://paymaster.fogo.io",
16
+ [Network.Mainnet]: "https://paymaster.dourolabs.app",
17
+ };
18
+ export var TransactionResultType;
19
+ (function (TransactionResultType) {
20
+ TransactionResultType[TransactionResultType["Success"] = 0] = "Success";
21
+ TransactionResultType[TransactionResultType["Failed"] = 1] = "Failed";
22
+ })(TransactionResultType || (TransactionResultType = {}));
23
+ const TransactionResult = {
24
+ Success: (signature) => ({
25
+ type: TransactionResultType.Success,
26
+ signature,
27
+ }),
28
+ Failed: (signature, error) => ({
29
+ type: TransactionResultType.Failed,
30
+ signature,
31
+ error,
32
+ }),
33
+ };
34
+ export const createSessionConnection = (options) => {
35
+ const rpcUrl = (options.rpc ?? DEFAULT_RPC[options.network]).toString();
36
+ const rpc = createSolanaRpc(rpcUrl);
37
+ const connection = new Web3Connection(rpcUrl, "confirmed");
38
+ const addressLookupTableCache = new Map();
39
+ return {
40
+ rpc,
41
+ connection,
42
+ network: options.network,
43
+ getSolanaConnection: createSolanaConnectionGetter(options.network),
44
+ sendToPaymaster: async (domain, sponsor, sessionKey, instructions, extraConfig) => {
45
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
46
+ const transaction = await buildTransaction(connection, latestBlockhash, sessionKey, sponsor, instructions, addressLookupTableCache, extraConfig);
47
+ return sendToPaymaster(options, domain, transaction);
48
+ },
49
+ getSponsor: (domain) => getSponsor(options, domain),
50
+ };
51
+ };
52
+ const createSolanaConnectionGetter = (network) => {
53
+ let connection;
54
+ return async () => {
55
+ if (connection === undefined) {
56
+ const url = new URL("https://api.fogo.io/api/solana-rpc");
57
+ url.searchParams.set("network", NETWORK_TO_QUERY_PARAM[network]);
58
+ const rpcUrlRes = await fetch(url);
59
+ if (rpcUrlRes.status === 200) {
60
+ const rpcUrl = await rpcUrlRes.text();
61
+ connection = new Web3Connection(rpcUrl);
62
+ }
63
+ else {
64
+ throw new Error("Failed to resolve Solana RPC url");
65
+ }
66
+ }
67
+ return connection;
68
+ };
69
+ };
70
+ const NETWORK_TO_QUERY_PARAM = {
71
+ [Network.Mainnet]: "mainnet",
72
+ [Network.Testnet]: "testnet",
73
+ };
74
+ const sendToPaymaster = async (options, domain, transaction) => {
75
+ if (options.sendToPaymaster === undefined) {
76
+ const url = new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER[options.network]);
77
+ url.searchParams.set("domain", domain);
78
+ const response = await fetch(url, {
79
+ method: "POST",
80
+ headers: {
81
+ "Content-Type": "application/json",
82
+ },
83
+ body: JSON.stringify({
84
+ transaction: getBase64EncodedWireTransaction(transaction),
85
+ }),
86
+ });
87
+ if (response.status === 200) {
88
+ return sponsorAndSendResponseSchema.parse(await response.json());
89
+ }
90
+ else {
91
+ throw new PaymasterResponseError(response.status, await response.text());
92
+ }
93
+ }
94
+ else {
95
+ return options.sendToPaymaster(transaction);
96
+ }
97
+ };
98
+ const buildTransaction = async (connection, latestBlockhash, sessionKey, sponsor, instructions, addressLookupTableCache, extraConfig) => {
99
+ const [signerKeys, addressLookupTable] = await Promise.all([
100
+ getSignerKeys(sessionKey, extraConfig?.extraSigners),
101
+ extraConfig?.addressLookupTable === undefined
102
+ ? Promise.resolve(undefined)
103
+ : getAddressLookupTable(connection, addressLookupTableCache, extraConfig.addressLookupTable),
104
+ ]);
105
+ if (Array.isArray(instructions)) {
106
+ const signers = await Promise.all(signerKeys.map((signer) => createSignerFromKeyPair(signer)));
107
+ return partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(fromLegacyPublicKey(sponsor), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions.map((instruction) => instruction instanceof TransactionInstruction
108
+ ? fromLegacyTransactionInstruction(instruction)
109
+ : instruction), tx), (tx) => addressLookupTable === undefined
110
+ ? tx
111
+ : compressTransactionMessageUsingAddressLookupTables(tx, {
112
+ [fromLegacyPublicKey(addressLookupTable.key)]: addressLookupTable.state.addresses.map((address) => fromLegacyPublicKey(address)),
113
+ }), (tx) => addSignersToTransactionMessage(signers, tx)));
114
+ }
115
+ else {
116
+ const tx = instructions instanceof VersionedTransaction
117
+ ? fromVersionedTransaction(instructions) // VersionedTransaction has a lifetime so it's fine to cast it so we can call partiallySignTransaction
118
+ : instructions;
119
+ return partiallySignTransaction(signerKeys, tx);
120
+ }
121
+ };
122
+ const getSignerKeys = async (sessionKey, extraSigners) => {
123
+ const extraSignerKeys = extraSigners === undefined
124
+ ? []
125
+ : await Promise.all(extraSigners.map((signer) => signer instanceof Keypair ? fromLegacyKeypair(signer) : signer));
126
+ return [
127
+ ...extraSignerKeys,
128
+ ...(sessionKey === undefined ? [] : [sessionKey]),
129
+ ];
130
+ };
131
+ const sponsorAndSendResponseSchema = z
132
+ .discriminatedUnion("type", [
133
+ z.object({
134
+ type: z.literal("success"),
135
+ signature: z.string(),
136
+ }),
137
+ z.object({
138
+ type: z.literal("failed"),
139
+ signature: z.string(),
140
+ error: z.object({
141
+ InstructionError: z.tuple([z.number(), z.unknown()]),
142
+ }),
143
+ }),
144
+ ])
145
+ .transform((data) => {
146
+ return data.type === "success"
147
+ ? TransactionResult.Success(data.signature)
148
+ : TransactionResult.Failed(data.signature, data.error);
149
+ });
150
+ const getSponsor = async (options, domain) => {
151
+ if (options.sponsor === undefined) {
152
+ const url = new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER[options.network]);
153
+ url.searchParams.set("domain", domain);
154
+ const response = await fetch(url);
155
+ if (response.status === 200) {
156
+ return new PublicKey(z.string().parse(await response.text()));
157
+ }
158
+ else {
159
+ throw new PaymasterResponseError(response.status, await response.text());
160
+ }
161
+ }
162
+ else {
163
+ return options.sponsor;
164
+ }
165
+ };
166
+ const getAddressLookupTable = async (connection, addressLookupTableCache, addressLookupTableAddress) => {
167
+ const value = addressLookupTableCache.get(addressLookupTableAddress);
168
+ if (value === undefined) {
169
+ const result = await connection.getAddressLookupTable(new PublicKey(addressLookupTableAddress));
170
+ if (result.value === null) {
171
+ return;
172
+ }
173
+ else {
174
+ addressLookupTableCache.set(addressLookupTableAddress, result.value);
175
+ return result.value;
176
+ }
177
+ }
178
+ else {
179
+ return value;
180
+ }
181
+ };
182
+ class PaymasterResponseError extends Error {
183
+ constructor(statusCode, message) {
184
+ super(`Paymaster sent a ${statusCode.toString()} response: ${message}`);
185
+ this.name = "PaymasterResponseError";
186
+ }
187
+ }
package/esm/context.d.ts CHANGED
@@ -1,40 +1,17 @@
1
- import type { Transaction, Instruction, TransactionWithLifetime } from "@solana/kit";
2
- import type { Connection, TransactionError } from "@solana/web3.js";
3
- import { PublicKey, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
4
- export type SessionContext = {
5
- chainId: string;
6
- connection: Connection;
7
- payer: PublicKey;
8
- domain: string;
9
- sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | Instruction)[] | VersionedTransaction | (Transaction & TransactionWithLifetime)) => Promise<TransactionResult>;
10
- };
11
- export declare enum TransactionResultType {
12
- Success = 0,
13
- Failed = 1
14
- }
15
- declare const TransactionResult: {
16
- Success: (signature: string) => {
17
- type: TransactionResultType.Success;
18
- signature: string;
19
- };
20
- Failed: (signature: string, error: TransactionError) => {
21
- type: TransactionResultType.Failed;
22
- signature: string;
23
- error: TransactionError;
24
- };
25
- };
26
- export type TransactionResult = ReturnType<(typeof TransactionResult)[keyof typeof TransactionResult]>;
1
+ import { Connection as Web3Connection } from "@solana/web3.js";
2
+ import type { Connection } from "./connection.js";
27
3
  export declare const createSessionContext: (options: {
28
4
  connection: Connection;
29
- addressLookupTableAddress?: string | undefined;
5
+ defaultAddressLookupTableAddress?: string | undefined;
30
6
  domain?: string | undefined;
31
- } & ({
32
- paymaster?: string | URL | undefined;
33
- sendToPaymaster?: undefined;
34
- sponsor?: undefined;
35
- } | {
36
- paymaster?: undefined;
37
- sendToPaymaster: (transaction: Transaction) => Promise<TransactionResult>;
38
- sponsor: PublicKey;
39
- })) => Promise<SessionContext>;
40
- export {};
7
+ }) => Promise<{
8
+ chainId: string;
9
+ domain: string;
10
+ payer: import("@solana/web3.js").PublicKey;
11
+ getSolanaConnection: () => Promise<Web3Connection>;
12
+ connection: Web3Connection;
13
+ rpc: import("@solana/kit").Rpc<import("@solana/kit").RequestAirdropApi & import("@solana/kit").GetAccountInfoApi & import("@solana/kit").GetBalanceApi & import("@solana/kit").GetBlockApi & import("@solana/kit").GetBlockCommitmentApi & import("@solana/kit").GetBlockHeightApi & import("@solana/kit").GetBlockProductionApi & import("@solana/kit").GetBlocksApi & import("@solana/kit").GetBlocksWithLimitApi & import("@solana/kit").GetBlockTimeApi & import("@solana/kit").GetClusterNodesApi & import("@solana/kit").GetEpochInfoApi & import("@solana/kit").GetEpochScheduleApi & import("@solana/kit").GetFeeForMessageApi & import("@solana/kit").GetFirstAvailableBlockApi & import("@solana/kit").GetGenesisHashApi & import("@solana/kit").GetHealthApi & import("@solana/kit").GetHighestSnapshotSlotApi & import("@solana/kit").GetIdentityApi & import("@solana/kit").GetInflationGovernorApi & import("@solana/kit").GetInflationRateApi & import("@solana/kit").GetInflationRewardApi & import("@solana/kit").GetLargestAccountsApi & import("@solana/kit").GetLatestBlockhashApi & import("@solana/kit").GetLeaderScheduleApi & import("@solana/kit").GetMaxRetransmitSlotApi & import("@solana/kit").GetMaxShredInsertSlotApi & import("@solana/kit").GetMinimumBalanceForRentExemptionApi & import("@solana/kit").GetMultipleAccountsApi & import("@solana/kit").GetProgramAccountsApi & import("@solana/kit").GetRecentPerformanceSamplesApi & import("@solana/kit").GetRecentPrioritizationFeesApi & import("@solana/kit").GetSignaturesForAddressApi & import("@solana/kit").GetSignatureStatusesApi & import("@solana/kit").GetSlotApi & import("@solana/kit").GetSlotLeaderApi & import("@solana/kit").GetSlotLeadersApi & import("@solana/kit").GetStakeMinimumDelegationApi & import("@solana/kit").GetSupplyApi & import("@solana/kit").GetTokenAccountBalanceApi & import("@solana/kit").GetTokenAccountsByDelegateApi & import("@solana/kit").GetTokenAccountsByOwnerApi & import("@solana/kit").GetTokenLargestAccountsApi & import("@solana/kit").GetTokenSupplyApi & import("@solana/kit").GetTransactionApi & import("@solana/kit").GetTransactionCountApi & import("@solana/kit").GetVersionApi & import("@solana/kit").GetVoteAccountsApi & import("@solana/kit").IsBlockhashValidApi & import("@solana/kit").MinimumLedgerSlotApi & import("@solana/kit").SendTransactionApi & import("@solana/kit").SimulateTransactionApi>;
14
+ network: import("./connection.js").Network;
15
+ sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: Parameters<typeof options.connection.sendToPaymaster>[3], extraConfig?: Parameters<typeof options.connection.sendToPaymaster>[4]) => Promise<import("./connection.js").TransactionResult>;
16
+ }>;
17
+ export type SessionContext = Awaited<ReturnType<typeof createSessionContext>>;