diff --git a/packages/core/docs/api/transactions.md b/packages/core/docs/api/transactions.md index 6ae9e8d46..fe7333fef 100644 --- a/packages/core/docs/api/transactions.md +++ b/packages/core/docs/api/transactions.md @@ -14,7 +14,7 @@ interface Transaction { ### Actions -Below are the 8 supported NEAR Actions used by `signAndSendTransaction` and `signAndSendTransactions`: +Below are the 8 supported NEAR Actions used by `signAndSendTransaction`, `signAndSendTransactions` and `signAndSendTransactionAsync`: ```ts interface CreateAccountAction { diff --git a/packages/core/docs/api/wallet.md b/packages/core/docs/api/wallet.md index c2295e0bc..65e1fa5a4 100644 --- a/packages/core/docs/api/wallet.md +++ b/packages/core/docs/api/wallet.md @@ -263,6 +263,45 @@ Signs one or more NEAR Actions before sending to the network. The user must be s })(); ``` +### `.signAndSendTransactionAsync(params)` + +**Parameters** + +- `params` (`object`) + - `signerId` (`string?`): Account ID used to sign the transaction. Defaults to the first account. + - `receiverId` (`string?`): Account ID to receive the transaction. Defaults to `contractId` defined in `.init`. + - `actions` (`Array`): NEAR Action(s) to sign and send to the network (e.g. `FunctionCall`). You can find more information on `Action` [here](./transactions.md). + - `callbackUrl` (`string?`): Applicable to browser wallets (e.g. MyNearWallet). This the callback url once the transaction is approved. + +**Returns** + +- `Promise`: Browser wallets won't return the transaction outcome as they may need to redirect for signing. More details on this can be found [here](https://docs.near.org/api/rpc/transactions#send-transaction-await). + +**Description** + +Signs one or more NEAR Actions before sending to the network. The user must be signed in to call this method as there's at least charges for gas spent. + +> Note: Sender only supports `"FunctionCall"` action types right now. If you wish to use other NEAR Actions in your dApp, it's recommended to remove this wallet in your configuration. + +**Example** + +```ts +(async () => { + const wallet = await selector.wallet("sender"); + const txHash = await wallet.signAndSendTransactionAsync({ + actions: [{ + type: "FunctionCall", + params: { + methodName: "addMessage", + args: { text: "Hello World!" }, + gas: "30000000000000", + deposit: "10000000000000000000000", + } + }] + }); +})(); +``` + ### `.signAndSendTransactions(params)` **Parameters** diff --git a/packages/core/docs/guides/custom-wallets.md b/packages/core/docs/guides/custom-wallets.md index 59d27f9d2..457f0b4de 100644 --- a/packages/core/docs/guides/custom-wallets.md +++ b/packages/core/docs/guides/custom-wallets.md @@ -58,6 +58,13 @@ const MyWallet: WalletBehaviourFactory = ({ return provider.sendTransaction(signedTx); }, + async signAndSendTransactionAsync({ signerId, receiverId, actions }) { + // Sign a list of NEAR Actions before sending via an RPC endpoint. + // An RPC provider is injected to make this process easier and configured based on options.network. + + return provider.sendTransactionAsync(signedTx); + }, + async signAndSendTransactions({ transactions }) { // Sign a list of Transactions before sending via an RPC endpoint. // An RPC provider is injected to make this process easier and configured based on options.network. @@ -144,6 +151,12 @@ Where you might have to construct NEAR Transactions and send them yourself, you > Note: Browser wallets (i.e. MyNearWallet) are unable to return the transaction outcome as they can trigger a redirect. The return type in this case is `Promise` instead of the usual `Promise`. +### `signAndSendTransactionAsync` + +This method is similar to `signAndSendTransaction` but instead returns the transaction hash as a `Uint8Array` instead of a `FinalExecutionOutcome`, allowing the users to monitor the transaction success or failure. + +> Note: Browser wallets (i.e. MyNearWallet) are unable to return the transaction outcome as they can trigger a redirect. The return type in this case is `Promise` instead of the usual `Uint8Array`. + ### `signAndSendTransactions` This method is similar to `signAndSendTransaction` but instead sends a batch of Transactions. @@ -154,3 +167,18 @@ This method is similar to `signAndSendTransaction` but instead sends a batch of This method allows users to sign a message for a specific recipient using their NEAR account. Returns the `SignedMessage` based on the [NEP413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md). + +### `signTransaction` + +This method is similar to `signMessage` but instead signs and returns a `SignedTransaction` which can be broadcasted to the network. + +This method composes and signs a SignedDelegate action to be executed in a transaction. Returns the `SignedDelegateWithHash` object. + +> Note: Browser wallets (i.e. MyNearWallet) are unable to return the transaction outcome as they can trigger a redirect. The return type in this case is `Promise` instead of the usual `Promise`. + + +### `sendTransaction` + +This method sends a previously signed transaction to the network. It takes a transaction hash and a signed transaction object, and optionally a callback URL. Returns a `FinalExecutionOutcome` when successful. + +> Note: Browser wallets (i.e. MyNearWallet) are unable to return the transaction outcome as they can trigger a redirect. The return type in this case is `Promise` instead of the usual `Promise`. diff --git a/packages/core/src/lib/services/provider/provider.service.ts b/packages/core/src/lib/services/provider/provider.service.ts index 6cf257b6b..49a296b29 100644 --- a/packages/core/src/lib/services/provider/provider.service.ts +++ b/packages/core/src/lib/services/provider/provider.service.ts @@ -50,6 +50,10 @@ export class Provider implements ProviderService { return this.provider.sendTransaction(signedTransaction); } + sendTransactionAsync(signedTransaction: SignedTransaction) { + return this.provider.sendTransactionAsync(signedTransaction); + } + private urlsToProviders(urls: Array) { return urls && urls.length > 0 ? urls.map((url) => new JsonRpcProvider({ url })) diff --git a/packages/core/src/lib/services/provider/provider.service.types.ts b/packages/core/src/lib/services/provider/provider.service.types.ts index ecc041d93..caf3de22d 100644 --- a/packages/core/src/lib/services/provider/provider.service.types.ts +++ b/packages/core/src/lib/services/provider/provider.service.types.ts @@ -23,4 +23,7 @@ export interface ProviderService { sendTransaction( signedTransaction: SignedTransaction ): Promise; + sendTransactionAsync( + signedTransaction: SignedTransaction + ): Promise; } diff --git a/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts b/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts index df954b930..041c049f6 100644 --- a/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts +++ b/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts @@ -301,6 +301,9 @@ export class WalletModules { const _signIn = wallet.signIn; const _signOut = wallet.signOut; const _signMessage = wallet.signMessage; + const _signTransaction = wallet.signTransaction; + const _sendTransaction = wallet.sendTransaction; + const _signDelegateAction = wallet.signDelegateAction; wallet.signIn = async (params: never) => { const accounts = await _signIn(params); @@ -332,6 +335,52 @@ export class WalletModules { return await _signMessage(params); }; + wallet.signTransaction = async (params: never) => { + if (_signTransaction === undefined) { + throw new Error( + `The signTransaction method is not supported by ${wallet.metadata.name}` + ); + } + + try { + return await _signTransaction(params); + } catch (error) { + throw new Error( + `Failed to sign transaction: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } + }; + + wallet.sendTransaction = async (signedTransaction: never) => { + if (_sendTransaction === undefined) { + throw new Error( + `The sendTransaction method is not supported by ${wallet.metadata.name}` + ); + } + + return await _sendTransaction(signedTransaction); + }; + + wallet.signDelegateAction = async (params: never) => { + if (_signDelegateAction === undefined) { + throw new Error( + `The signDelegateAction method is not supported by ${wallet.metadata.name}` + ); + } + + try { + return await _signDelegateAction(params); + } catch (error) { + throw new Error( + `Failed to sign delegate action: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } + }; + return wallet; } diff --git a/packages/core/src/lib/wallet/wallet.types.ts b/packages/core/src/lib/wallet/wallet.types.ts index 9ef2c7cae..20c604067 100644 --- a/packages/core/src/lib/wallet/wallet.types.ts +++ b/packages/core/src/lib/wallet/wallet.types.ts @@ -10,6 +10,8 @@ import type { ReadOnlyStore } from "../store.types"; import type { Transaction, Action } from "./transactions.types"; import type { Modify, Optional } from "../utils.types"; import type { FinalExecutionOutcome } from "near-api-js/lib/providers"; +import type { SignedTransaction, Signature } from "near-api-js/lib/transaction"; +import type { PublicKey } from "near-api-js/lib/utils"; interface BaseWalletMetadata { /** @@ -121,6 +123,65 @@ interface SignAndSendTransactionsParams { transactions: Array>; } +interface SignTransactionParams { + /** + * The NEAR account ID of the transaction receiver. + */ + receiverId: string; + /** + * NEAR Action(s) to sign and send to the network (e.g. `FunctionCall`). You can find more information on `Action` {@link https://github.com/near/wallet-selector/blob/main/packages/core/docs/api/transactions.md | here}. + */ + actions: Array; + /** + * The human-readable NEAR account name + */ + accountId?: string; + /** + * The targeted network. (ex. default, betanet, etc…) + */ + networkId?: string; + callbackUrl?: string; +} + +export interface SignDelegateActionParams { + blockHeightTtl: number; + receiverId: string; + actions: Array; + callbackUrl?: string; +} + +interface DelegateAction { + /** + * Account ID for the intended signer of the delegate action + */ + senderId: string; + /** + * The set of actions to be included in the meta transaction + */ + actions: Array; + /** + * Account ID for the intended receiver of the meta transaction + */ + receiverId: string; + /** + * Current nonce on the access key used to sign the delegate action + */ + nonce: bigint; + /** + * The maximum block height for which this action can be executed as part of a transaction + */ + maxBlockHeight: bigint; + /** + * Public key for the access key used to sign the delegate action + */ + publicKey: PublicKey; +} + +export interface SignedDelegate { + delegateAction: DelegateAction; + signature: Signature; +} + interface BaseWalletBehaviour { /** * Programmatically sign in. Hardware wallets (e.g. Ledger) require `derivationPaths` to validate access key permissions. @@ -147,6 +208,13 @@ interface BaseWalletBehaviour { signAndSendTransaction( params: SignAndSendTransactionParams ): Promise; + /** + * Signs one or more NEAR Actions before sending to the network. + * The user must be signed in to call this method as there's at least charges for gas spent. + */ + signAndSendTransactionAsync?( + params: SignAndSendTransactionParams + ): Promise; /** * Signs one or more transactions before sending to the network. * The user must be signed in to call this method as there's at least charges for gas spent. @@ -155,6 +223,27 @@ interface BaseWalletBehaviour { params: SignAndSendTransactionsParams ): Promise>; signMessage?(params: SignMessageParams): Promise; + /** + * Signs one or more NEAR Actions which can be broadcasted to the network. + * The user must be signed in to call this method. + */ + signTransaction?( + params: SignTransactionParams + ): Promise<[Uint8Array, SignedTransaction] | void>; + /** + * Sends a signed transaction to the network. + */ + sendTransaction?(params: { + hash: Uint8Array; + signedTransaction: SignedTransaction; + callbackUrl?: string; + }): Promise; + /** + * Composes and signs a Signed Delegate Action to be executed in a transaction + */ + signDelegateAction?( + params: SignDelegateActionParams + ): Promise; } type BaseWallet< @@ -242,9 +331,23 @@ export type BrowserWalletBehaviour = Modify< signAndSendTransaction( params: BrowserWalletSignAndSendTransactionParams ): Promise; + signAndSendTransactionAsync?( + params: BrowserWalletSignAndSendTransactionParams + ): Promise; signAndSendTransactions( params: BrowserWalletSignAndSendTransactionsParams ): Promise; + signTransaction?( + params: SignTransactionParams + ): Promise<[Uint8Array, SignedTransaction] | void>; + sendTransaction?(params: { + hash: Uint8Array; + signedTransaction: SignedTransaction; + callbackUrl?: string; + }): Promise; + signDelegateAction?( + params: SignDelegateActionParams + ): Promise; } >;