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

feat: Add signTransaction() and signDelegateAction() to BaseWalletBehaviour interface #1213

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion packages/core/docs/api/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
39 changes: 39 additions & 0 deletions packages/core/docs/api/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Action>`): 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<Uint8Array | void>`: 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**
Expand Down
28 changes: 28 additions & 0 deletions packages/core/docs/guides/custom-wallets.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ const MyWallet: WalletBehaviourFactory<BrowserWallet> = ({
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.
Expand Down Expand Up @@ -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<void>` instead of the usual `Promise<FinalExecutionOutcome>`.

### `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<void>` instead of the usual `Uint8Array`.

### `signAndSendTransactions`

This method is similar to `signAndSendTransaction` but instead sends a batch of Transactions.
Expand All @@ -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<void>` instead of the usual `Promise<SignedDelegateWithHash>`.


### `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<void>` instead of the usual `Promise<FinalExecutionOutcome>`.
4 changes: 4 additions & 0 deletions packages/core/src/lib/services/provider/provider.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>) {
return urls && urls.length > 0
? urls.map((url) => new JsonRpcProvider({ url }))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ export interface ProviderService {
sendTransaction(
signedTransaction: SignedTransaction
): Promise<FinalExecutionOutcome>;
sendTransactionAsync(
signedTransaction: SignedTransaction
): Promise<FinalExecutionOutcome>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down
103 changes: 103 additions & 0 deletions packages/core/src/lib/wallet/wallet.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -121,6 +123,65 @@ interface SignAndSendTransactionsParams {
transactions: Array<Optional<Transaction, "signerId">>;
}

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<Action>;
/**
* 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<Action>;
callbackUrl?: string;
}

interface DelegateAction {
/**
* Account ID for the intended signer of the delegate action
*/
senderId: string;
Comment on lines +154 to +157
Copy link
Collaborator

Choose a reason for hiding this comment

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

App developer does not know the senderId

/**
* The set of actions to be included in the meta transaction
*/
actions: Array<Action>;
/**
* 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;
Comment on lines +166 to +169
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same problem as with transactions, see above

/**
* 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;
Comment on lines +174 to +177
Copy link
Collaborator

Choose a reason for hiding this comment

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

App developer does not have the user's public key

}

export interface SignedDelegate {
delegateAction: DelegateAction;
signature: Signature;
}

interface BaseWalletBehaviour {
/**
* Programmatically sign in. Hardware wallets (e.g. Ledger) require `derivationPaths` to validate access key permissions.
Expand All @@ -147,6 +208,13 @@ interface BaseWalletBehaviour {
signAndSendTransaction(
params: SignAndSendTransactionParams
): Promise<providers.FinalExecutionOutcome>;
/**
* 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<string>;
/**
* 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.
Expand All @@ -155,6 +223,27 @@ interface BaseWalletBehaviour {
params: SignAndSendTransactionsParams
): Promise<Array<providers.FinalExecutionOutcome>>;
signMessage?(params: SignMessageParams): Promise<SignedMessage | void>;
/**
* 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<providers.FinalExecutionOutcome>;
/**
* Composes and signs a Signed Delegate Action to be executed in a transaction
*/
signDelegateAction?(
params: SignDelegateActionParams
): Promise<SignedDelegate | void>;
}

type BaseWallet<
Expand Down Expand Up @@ -242,9 +331,23 @@ export type BrowserWalletBehaviour = Modify<
signAndSendTransaction(
params: BrowserWalletSignAndSendTransactionParams
): Promise<FinalExecutionOutcome | void>;
signAndSendTransactionAsync?(
params: BrowserWalletSignAndSendTransactionParams
): Promise<string | void>;
signAndSendTransactions(
params: BrowserWalletSignAndSendTransactionsParams
): Promise<void>;
signTransaction?(
params: SignTransactionParams
): Promise<[Uint8Array, SignedTransaction] | void>;
sendTransaction?(params: {
hash: Uint8Array;
signedTransaction: SignedTransaction;
callbackUrl?: string;
}): Promise<FinalExecutionOutcome | void>;
signDelegateAction?(
params: SignDelegateActionParams
): Promise<SignedDelegate | void>;
}
>;

Expand Down
Loading