Skip to content

Commit

Permalink
Use wallet for transaction broadcasting (#601)
Browse files Browse the repository at this point in the history
* adds broadcast ability within wallet adapter

* add support for native transaction sending

* attempt to fix glow wallet

* remove unused
  • Loading branch information
macalinao authored May 26, 2022
1 parent 0780b24 commit 61e2c4d
Show file tree
Hide file tree
Showing 13 changed files with 489 additions and 87 deletions.
38 changes: 36 additions & 2 deletions packages/solana-contrib/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
Transaction,
} from "@solana/web3.js";

import type { PendingTransaction } from ".";
import type { BroadcastOptions, PendingTransaction } from ".";

/**
* Wallet interface for objects that can be used to sign provider transactions.
Expand Down Expand Up @@ -103,7 +103,7 @@ export interface Broadcaster {
*/
broadcast: (
tx: Transaction,
opts?: ConfirmOptions
opts?: BroadcastOptions
) => Promise<PendingTransaction>;

/**
Expand All @@ -121,12 +121,34 @@ export interface Broadcaster {
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>>;
}

/**
* Sign and broadcast options.
*/
export type SignAndBroadcastOptions = BroadcastOptions & {
/**
* Additional signers
*/
signers?: Signer[];
};

/**
* An interface that can sign transactions.
*/
export interface TransactionSigner {
publicKey: PublicKey;

/**
* Signs and broadcasts a transaction.
*
* @param transaction
* @param broadcaster
* @param options
*/
signAndBroadcastTransaction(
transaction: Transaction,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction>;

/**
* Signs the given transaction, paid for and signed by the provider's wallet.
*
Expand Down Expand Up @@ -204,6 +226,18 @@ export interface Provider extends ReadonlyProvider {
opts?: ConfirmOptions
) => Promise<PendingTransaction[]>;

/**
* Signs and broadcasts a transaction.
*
* @param transaction
* @param broadcaster
* @param options
*/
signAndBroadcastTransaction(
transaction: Transaction,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction>;

/**
* Simulates the given transaction, returning emitted logs from execution.
*
Expand Down
52 changes: 45 additions & 7 deletions packages/solana-contrib/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { SingleConnectionBroadcaster } from "./broadcaster";
import type {
Provider,
SendTxRequest,
SignAndBroadcastOptions,
TransactionSigner,
Wallet,
} from "./interfaces";
Expand Down Expand Up @@ -77,6 +78,19 @@ export class SolanaReadonlyProvider implements ReadonlyProvider {
}
}

export const doSignAndBroadcastTransaction = async (
wallet: Pick<Wallet, "signTransaction">,
transaction: Transaction,
broadcaster: Broadcaster,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction> => {
const tx = await wallet.signTransaction(transaction);
if (opts?.signers && opts.signers.length > 0) {
tx.sign(...opts.signers);
}
return await broadcaster.broadcast(tx, opts);
};

/**
* Signs Solana transactions.
*/
Expand All @@ -91,6 +105,18 @@ export class SolanaTransactionSigner implements TransactionSigner {
return this.wallet.publicKey;
}

async signAndBroadcastTransaction(
transaction: Transaction,
opts?: SignAndBroadcastOptions | undefined
): Promise<PendingTransaction> {
return await doSignAndBroadcastTransaction(
this.wallet,
transaction,
this.broadcaster,
opts
);
}

/**
* Sends the given transaction, paid for and signed by the provider's wallet.
*
Expand Down Expand Up @@ -160,8 +186,6 @@ export class SolanaTransactionSigner implements TransactionSigner {
* This implementation was taken from Anchor.
*/
export class SolanaProvider extends SolanaReadonlyProvider implements Provider {
readonly signer: TransactionSigner;

/**
* @param connection The cluster connection where the program is deployed.
* @param sendConnection The connection where transactions are sent to.
Expand All @@ -172,14 +196,21 @@ export class SolanaProvider extends SolanaReadonlyProvider implements Provider {
override readonly connection: Connection,
readonly broadcaster: Broadcaster,
override readonly wallet: Wallet,
override readonly opts: ConfirmOptions = DEFAULT_PROVIDER_OPTIONS
) {
super(connection, opts);
this.signer = new SolanaTransactionSigner(
override readonly opts: ConfirmOptions = DEFAULT_PROVIDER_OPTIONS,
readonly signer: TransactionSigner = new SolanaTransactionSigner(
wallet,
broadcaster,
opts.preflightCommitment
);
)
) {
super(connection, opts);
}

async signAndBroadcastTransaction(
transaction: Transaction,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction> {
return await this.signer.signAndBroadcastTransaction(transaction, opts);
}

/**
Expand Down Expand Up @@ -384,6 +415,13 @@ export class SolanaAugmentedProvider implements AugmentedProvider {
return this.provider.wallet;
}

signAndBroadcastTransaction(
transaction: Transaction,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction> {
return this.provider.signAndBroadcastTransaction(transaction, opts);
}

send(
tx: Transaction,
signers?: (Signer | undefined)[] | undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import type Transport from "@ledgerhq/hw-transport";
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
import type { PublicKey, Transaction } from "@solana/web3.js";
import type {
Broadcaster,
PendingTransaction,
SignAndBroadcastOptions,
} from "@saberhq/solana-contrib";
import { doSignAndBroadcastTransaction } from "@saberhq/solana-contrib";
import type { Connection, PublicKey, Transaction } from "@solana/web3.js";
import EventEmitter from "eventemitter3";

import type { WalletAdapter } from "../types";
import type { ConnectedWallet, WalletAdapter } from "../types";
import { getPublicKey, getSolanaDerivationPath, signTransaction } from "./core";

const DEFAULT_DERIVATION_PATH = getSolanaDerivationPath();
Expand Down Expand Up @@ -42,6 +48,20 @@ export class LedgerWalletAdapter extends EventEmitter implements WalletAdapter {
return false;
}

async signAndBroadcastTransaction(
transaction: Transaction,
_connection: Connection,
broadcaster: Broadcaster,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction> {
return await doSignAndBroadcastTransaction(
this as ConnectedWallet,
transaction,
broadcaster,
opts
);
}

async signAllTransactions(
transactions: Transaction[]
): Promise<Transaction[]> {
Expand Down
19 changes: 18 additions & 1 deletion packages/use-solana/src/adapters/readonly/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { Transaction } from "@solana/web3.js";
import type {
Broadcaster,
BroadcastOptions,
PendingTransaction,
} from "@saberhq/solana-contrib";
import type { Connection, Transaction } from "@solana/web3.js";
import { PublicKey } from "@solana/web3.js";
import { EventEmitter } from "eventemitter3";

Expand All @@ -21,6 +26,9 @@ export const setReadonlySolanaPubkey = (pubkey: PublicKey): void => {
window.USE_SOLANA_PUBKEY_OVERRIDE = pubkey.toString();
};

/**
* Adapter that cannot sign transactions. Dummy for testing.
*/
export class ReadonlyAdapter extends EventEmitter implements WalletAdapter {
private _publicKey: PublicKey | null = null;

Expand Down Expand Up @@ -49,6 +57,15 @@ export class ReadonlyAdapter extends EventEmitter implements WalletAdapter {
return this._publicKey;
}

signAndBroadcastTransaction(
_transaction: Transaction,
_connection: Connection,
_broadcaster: Broadcaster,
_opts?: BroadcastOptions
): Promise<PendingTransaction> {
throw new Error("readonly adapter cannot sign transactions");
}

signAllTransactions(_transactions: Transaction[]): Promise<Transaction[]> {
throw new Error("readonly adapter cannot sign transactions");
}
Expand Down
31 changes: 28 additions & 3 deletions packages/use-solana/src/adapters/secret-key/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { SignerWallet } from "@saberhq/solana-contrib";
import type { PublicKey, Transaction } from "@solana/web3.js";
import type {
Broadcaster,
PendingTransaction,
SignAndBroadcastOptions,
} from "@saberhq/solana-contrib";
import {
doSignAndBroadcastTransaction,
SignerWallet,
} from "@saberhq/solana-contrib";
import type { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { Keypair } from "@solana/web3.js";
import EventEmitter from "eventemitter3";

import type { WalletAdapter } from "../types";
import type { ConnectedWallet, WalletAdapter } from "../types";

/**
* Adapter backed by a secret key.
*/
export class SecretKeyAdapter extends EventEmitter implements WalletAdapter {
_wallet?: SignerWallet;
_publicKey?: PublicKey;
Expand All @@ -24,6 +35,20 @@ export class SecretKeyAdapter extends EventEmitter implements WalletAdapter {
return false;
}

async signAndBroadcastTransaction(
transaction: Transaction,
_connection: Connection,
broadcaster: Broadcaster,
opts?: SignAndBroadcastOptions
): Promise<PendingTransaction> {
return await doSignAndBroadcastTransaction(
this as ConnectedWallet,
transaction,
broadcaster,
opts
);
}

signAllTransactions(transactions: Transaction[]): Promise<Transaction[]> {
const wallet = this._wallet;
if (!wallet) {
Expand Down
Loading

0 comments on commit 61e2c4d

Please sign in to comment.