Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP of signAndSendTransaction + signAndSendAllTransactions API #841

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions packages/core/base/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Connection, PublicKey, SendOptions, Signer, Transaction, TransactionSignature } from '@solana/web3.js';
import type { Commitment, Connection, PublicKey, Signer, Transaction, TransactionSignature } from '@solana/web3.js';
import EventEmitter from 'eventemitter3';
import { type WalletError, WalletNotConnectedError } from './errors.js';
import { WalletNotConnectedError, type WalletError } from './errors.js';
import type { SupportedTransactionVersions, TransactionOrVersionedTransaction } from './transaction.js';

export { EventEmitter };
Expand All @@ -12,10 +12,29 @@ export interface WalletAdapterEvents {
readyStateChange(readyState: WalletReadyState): void;
}

export interface SendTransactionOptions extends SendOptions {
export interface SignAndSendTransactionOptions extends SendOptions {
signers?: Signer[];
}

export interface SendOptions {
minContextSlot?: number;
/** @deprecated Wallets are not expected to support this option. */
skipPreflight?: boolean;
/** @deprecated Wallets are not expected to support this option. */
preflightCommitment?: Commitment;
/** @deprecated Wallets are not expected to support this option. */
maxRetries?: number;
}

/** @deprecated Use `SignAndSendTransactionOptions` instead. */
export type SendTransactionOptions = SignAndSendTransactionOptions;

export interface SignAndSendAllTransactionsError {
Copy link
Contributor

@0xproflupin 0xproflupin Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not name this SignAndSendTransactionError. This could very well be used for signAndSend, because ideally wallets should confirm transactions before returning sigs

type: string;
code: number;
message: string;
}

// WalletName is a nominal type that wallet adapters should use, e.g. `'MyCryptoWallet' as WalletName<'MyCryptoWallet'>`
// https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d
export type WalletName<T extends string = string> = T & { __brand__: 'WalletName' };
Expand All @@ -33,10 +52,21 @@ export interface WalletAdapterProps<Name extends string = string> {
autoConnect(): Promise<void>;
connect(): Promise<void>;
disconnect(): Promise<void>;
signAndSendTransaction(
transaction: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>,
connection: Connection,
options?: SignAndSendTransactionOptions
): Promise<TransactionSignature>;
signAndSendAllTransactions(
transactions: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>[],
connection: Connection,
options?: SignAndSendTransactionOptions
): Promise<(TransactionSignature | SignAndSendAllTransactionsError)[]>;
/** @deprecated Use `signAndSendTransaction` instead. */
sendTransaction(
transaction: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>,
connection: Connection,
options?: SendTransactionOptions
options?: SignAndSendTransactionOptions
): Promise<TransactionSignature>;
}

Expand Down Expand Up @@ -94,12 +124,39 @@ export abstract class BaseWalletAdapter<Name extends string = string>
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>;

abstract sendTransaction(
abstract signAndSendTransaction(
transaction: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>,
connection: Connection,
options?: SendTransactionOptions
options?: SignAndSendTransactionOptions
): Promise<TransactionSignature>;

async signAndSendAllTransactions(
transactions: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>[],
connection: Connection,
options?: SignAndSendTransactionOptions | undefined
): Promise<(TransactionSignature | SignAndSendAllTransactionsError)[]> {
const results = await Promise.allSettled(
transactions.map((transaction) => this.signAndSendTransaction(transaction, connection, options))
);
return results.map((result) => {
if (result.status === 'fulfilled') return result.value;
return {
type: result.reason.type || result.reason.name,
code: result.reason.code,
message: result.reason.message,
};
});
}

/** @deprecated Use `signAndSendTransaction` instead. */
sendTransaction(
transaction: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>,
connection: Connection,
options?: SignAndSendTransactionOptions
): Promise<TransactionSignature> {
return this.signAndSendTransaction(transaction, connection, options);
}

protected async prepareTransaction(
transaction: Transaction,
connection: Connection,
Expand Down
6 changes: 3 additions & 3 deletions packages/core/base/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { SolanaSignInInput, SolanaSignInOutput } from '@solana/wallet-stand
import type { Connection, TransactionSignature } from '@solana/web3.js';
import {
BaseWalletAdapter,
type SendTransactionOptions,
type SignAndSendTransactionOptions,
type WalletAdapter,
type WalletAdapterProps,
} from './adapter.js';
Expand All @@ -24,10 +24,10 @@ export abstract class BaseSignerWalletAdapter<Name extends string = string>
extends BaseWalletAdapter<Name>
implements SignerWalletAdapter<Name>
{
async sendTransaction(
async signAndSendTransaction(
transaction: TransactionOrVersionedTransaction<this['supportedTransactionVersions']>,
connection: Connection,
options: SendTransactionOptions = {}
options: SignAndSendTransactionOptions = {}
): Promise<TransactionSignature> {
let emit = true;
try {
Expand Down
23 changes: 19 additions & 4 deletions packages/core/react/src/WalletProviderBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,22 @@ export function WalletProviderBase({
})();
}, [connected, onAutoConnectRequest, onConnectError, wallet]);

// Send a transaction using the provided connection
const sendTransaction: WalletAdapterProps['sendTransaction'] = useCallback(
// Sign and send a transaction using the provided connection
const signAndSendTransaction: WalletAdapterProps['signAndSendTransaction'] = useCallback(
async (transaction, connection, options) => {
if (!adapter) throw handleErrorRef.current(new WalletNotSelectedError());
if (!connected) throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.sendTransaction(transaction, connection, options);
return await adapter.signAndSendTransaction(transaction, connection, options);
},
[adapter, connected]
);

// Sign and send multiple transactions using the provided connection
const signAndSendAllTransactions: WalletAdapterProps['signAndSendAllTransactions'] = useCallback(
async (transactions, connection, options) => {
if (!adapter) throw handleErrorRef.current(new WalletNotSelectedError());
if (!connected) throw handleErrorRef.current(new WalletNotConnectedError(), adapter);
return await adapter.signAndSendAllTransactions(transactions, connection, options);
},
[adapter, connected]
);
Expand Down Expand Up @@ -251,6 +261,9 @@ export function WalletProviderBase({
[adapter]
);

// Deprecated alias for `signAndSendTransaction`.
const sendTransaction: WalletAdapterProps['sendTransaction'] = signAndSendTransaction;

const handleConnect = useCallback(async () => {
if (isConnectingRef.current || isDisconnectingRef.current || wallet?.adapter.connected) return;
if (!wallet) throw handleErrorRef.current(new WalletNotSelectedError());
Expand Down Expand Up @@ -296,11 +309,13 @@ export function WalletProviderBase({
select: onSelectWallet,
connect: handleConnect,
disconnect: handleDisconnect,
sendTransaction,
signAndSendTransaction,
signAndSendAllTransactions,
signTransaction,
signAllTransactions,
signMessage,
signIn,
sendTransaction,
}}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/react/src/__mocks__/MockWalletAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export abstract class MockWalletAdapter extends BaseWalletAdapter {
this.emit('disconnect');
});
});
sendTransaction = jest.fn();
signAndSendTransaction = jest.fn();
supportedTransactionVersions = null;
autoConnect = jest.fn();
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe('WalletProviderBase', () => {
this.emit('disconnect');
});
});
sendTransaction = jest.fn();
signAndSendTransaction = jest.fn();
supportedTransactionVersions = null;
}
class FooWalletAdapter extends MockWalletAdapter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ describe('WalletProvider when the environment is `MOBILE_WEB`', () => {
this.emit('disconnect');
});
});
sendTransaction = jest.fn();
signAndSendTransaction = jest.fn();
supportedTransactionVersions = null;
autoConnect = jest.fn();
}
Expand Down
15 changes: 12 additions & 3 deletions packages/core/react/src/useWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ export interface WalletContextState {
connect(): Promise<void>;
disconnect(): Promise<void>;

sendTransaction: WalletAdapterProps['sendTransaction'];
signAndSendTransaction: WalletAdapterProps['signAndSendTransaction'];
signAndSendAllTransactions: WalletAdapterProps['signAndSendAllTransactions'];
signTransaction: SignerWalletAdapterProps['signTransaction'] | undefined;
signAllTransactions: SignerWalletAdapterProps['signAllTransactions'] | undefined;
signMessage: MessageSignerWalletAdapterProps['signMessage'] | undefined;
signIn: SignInMessageSignerWalletAdapterProps['signIn'] | undefined;
/** @deprecated Use `signAndSendTransaction` instead. */
sendTransaction: WalletAdapterProps['sendTransaction'];
}

const EMPTY_ARRAY: ReadonlyArray<never> = [];
Expand All @@ -51,8 +54,11 @@ const DEFAULT_CONTEXT: Partial<WalletContextState> = {
disconnect() {
return Promise.reject(logMissingProviderError('call', 'disconnect'));
},
sendTransaction() {
return Promise.reject(logMissingProviderError('call', 'sendTransaction'));
signAndSendTransaction() {
return Promise.reject(logMissingProviderError('call', 'signAndSendTransaction'));
},
signAndSendAllTransactions() {
return Promise.reject(logMissingProviderError('call', 'signAndSendAllTransactions'));
},
signTransaction() {
return Promise.reject(logMissingProviderError('call', 'signTransaction'));
Expand All @@ -66,6 +72,9 @@ const DEFAULT_CONTEXT: Partial<WalletContextState> = {
signIn() {
return Promise.reject(logMissingProviderError('call', 'signIn'));
},
sendTransaction() {
return Promise.reject(logMissingProviderError('call', 'sendTransaction'));
},
};
Object.defineProperty(DEFAULT_CONTEXT, 'wallets', {
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useNotify } from './notify';

export const SendLegacyTransaction: FC = () => {
const { connection } = useConnection();
const { publicKey, sendTransaction, wallet } = useWallet();
const { publicKey, signAndSendTransaction, wallet } = useWallet();
const notify = useNotify();
const supportedTransactionVersions = wallet?.adapter.supportedTransactionVersions;

Expand Down Expand Up @@ -38,15 +38,15 @@ export const SendLegacyTransaction: FC = () => {
});
const transaction = new VersionedTransaction(message.compileToLegacyMessage());

signature = await sendTransaction(transaction, connection, { minContextSlot });
signature = await signAndSendTransaction(transaction, connection, { minContextSlot });
notify('info', 'Transaction sent:', signature);

await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });
notify('success', 'Transaction successful!', signature);
} catch (error: any) {
notify('error', `Transaction failed! ${error?.message}`, signature);
}
}, [publicKey, supportedTransactionVersions, connection, sendTransaction, notify]);
}, [publicKey, supportedTransactionVersions, connection, signAndSendTransaction, notify]);

return (
<Button
Expand Down
6 changes: 3 additions & 3 deletions packages/starter/example/src/components/SendTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useNotify } from './notify';

export const SendTransaction: FC = () => {
const { connection } = useConnection();
const { publicKey, sendTransaction } = useWallet();
const { publicKey, signAndSendTransaction } = useWallet();
const notify = useNotify();

const onClick = useCallback(async () => {
Expand All @@ -32,15 +32,15 @@ export const SendTransaction: FC = () => {
})
);

signature = await sendTransaction(transaction, connection, { minContextSlot });
signature = await signAndSendTransaction(transaction, connection, { minContextSlot });
notify('info', 'Transaction sent:', signature);

await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });
notify('success', 'Transaction successful!', signature);
} catch (error: any) {
notify('error', `Transaction failed! ${error?.message}`, signature);
}
}, [publicKey, connection, sendTransaction, notify]);
}, [publicKey, connection, signAndSendTransaction, notify]);

return (
<Button variant="contained" color="secondary" onClick={onClick} disabled={!publicKey}>
Expand Down
6 changes: 3 additions & 3 deletions packages/starter/example/src/components/SendV0Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useNotify } from './notify';

export const SendV0Transaction: FC = () => {
const { connection } = useConnection();
const { publicKey, sendTransaction, wallet } = useWallet();
const { publicKey, signAndSendTransaction, wallet } = useWallet();
const notify = useNotify();
const supportedTransactionVersions = wallet?.adapter.supportedTransactionVersions;

Expand Down Expand Up @@ -51,15 +51,15 @@ export const SendV0Transaction: FC = () => {
});
const transaction = new VersionedTransaction(message.compileToV0Message([lookupTable]));

signature = await sendTransaction(transaction, connection, { minContextSlot });
signature = await signAndSendTransaction(transaction, connection, { minContextSlot });
notify('info', 'Transaction sent:', signature);

await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });
notify('success', 'Transaction successful!', signature);
} catch (error: any) {
notify('error', `Transaction failed! ${error?.message}`, signature);
}
}, [publicKey, supportedTransactionVersions, connection, sendTransaction, notify]);
}, [publicKey, supportedTransactionVersions, connection, signAndSendTransaction, notify]);

return (
<Button
Expand Down
11 changes: 6 additions & 5 deletions packages/wallets/alpha/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { EventEmitter, SendTransactionOptions, WalletName } from '@solana/wallet-adapter-base';
import type { EventEmitter, SignAndSendTransactionOptions, WalletName } from '@solana/wallet-adapter-base';
import {
BaseMessageSignerWalletAdapter,
scopePollingDetectionStrategy,
SendOptions,
WalletAccountError,
WalletConnectionError,
WalletDisconnectedError,
Expand All @@ -14,8 +14,9 @@ import {
WalletSendTransactionError,
WalletSignMessageError,
WalletSignTransactionError,
scopePollingDetectionStrategy,
} from '@solana/wallet-adapter-base';
import type { Connection, SendOptions, Transaction, TransactionSignature } from '@solana/web3.js';
import type { Connection, Transaction, TransactionSignature } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';

interface AlphaWalletEvents {
Expand Down Expand Up @@ -156,10 +157,10 @@ export class AlphaWalletAdapter extends BaseMessageSignerWalletAdapter {
this.emit('disconnect');
}

async sendTransaction(
async signAndSendTransaction(
transaction: Transaction,
connection: Connection,
options: SendTransactionOptions = {}
options: SignAndSendTransactionOptions = {}
): Promise<TransactionSignature> {
try {
const wallet = this._wallet;
Expand Down
11 changes: 6 additions & 5 deletions packages/wallets/avana/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { EventEmitter, SendTransactionOptions, WalletName } from '@solana/wallet-adapter-base';
import type { EventEmitter, SignAndSendTransactionOptions, WalletName } from '@solana/wallet-adapter-base';
import {
BaseMessageSignerWalletAdapter,
scopePollingDetectionStrategy,
SendOptions,
WalletAccountError,
WalletConnectionError,
WalletDisconnectedError,
Expand All @@ -14,8 +14,9 @@ import {
WalletSendTransactionError,
WalletSignMessageError,
WalletSignTransactionError,
scopePollingDetectionStrategy,
} from '@solana/wallet-adapter-base';
import type { Connection, SendOptions, Transaction, TransactionSignature } from '@solana/web3.js';
import type { Connection, Transaction, TransactionSignature } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';

interface AvanaWalletEvents {
Expand Down Expand Up @@ -158,10 +159,10 @@ export class AvanaWalletAdapter extends BaseMessageSignerWalletAdapter {
this.emit('disconnect');
}

async sendTransaction(
async signAndSendTransaction(
transaction: Transaction,
connection: Connection,
options: SendTransactionOptions = {}
options: SignAndSendTransactionOptions = {}
): Promise<TransactionSignature> {
try {
const wallet = this._wallet;
Expand Down
Loading