From cb6a025f308f97bd68484530d305643c99b52cdb Mon Sep 17 00:00:00 2001 From: Greg Nazario Date: Sun, 10 Sep 2023 08:06:48 +0800 Subject: [PATCH] [fee-payer] Cleanup multi-agent APIs to now just be for raw transactions --- apps/nextjs-example/pages/index.tsx | 71 ++++++++++----- .../wallet-adapter-core/src/WalletCore.ts | 87 +++++++------------ packages/wallet-adapter-core/src/types.ts | 51 +++++++++-- .../src/WalletProvider.tsx | 38 ++++---- .../wallet-adapter-react/src/useWallet.tsx | 20 ++--- 5 files changed, 150 insertions(+), 117 deletions(-) diff --git a/apps/nextjs-example/pages/index.tsx b/apps/nextjs-example/pages/index.tsx index 9b919dd9..9fdff31e 100644 --- a/apps/nextjs-example/pages/index.tsx +++ b/apps/nextjs-example/pages/index.tsx @@ -15,7 +15,11 @@ import dynamic from "next/dynamic"; import Image from "next/image"; import {useAutoConnect} from "../components/AutoConnectProvider"; import {useAlert} from "../components/AlertProvider"; -import {AccountInfo, NetworkInfo, WalletInfo} from "@aptos-labs/wallet-adapter-core"; +import { + AccountInfo, + NetworkInfo, + WalletInfo +} from "@aptos-labs/wallet-adapter-core"; import {useState} from "react"; const FEE_PAYER_ACCOUNT_KEY = "feePayerAccountPrivateKey"; @@ -35,11 +39,13 @@ const WalletSelectorAntDesign = dynamic( ); const aptosClient = (network?: string) => { - if (network === NetworkName.Devnet.toLowerCase()) { + if (isDevnet(network)) { return DEVNET_CLIENT; - } else if (network === NetworkName.Testnet.toLowerCase()) { + }else if (isTestnet(network)) { return TESTNET_CLIENT; - } else if (network === NetworkName.Mainnet.toLowerCase()) { + } else if (isLocal(network)) { + return LOCAL_CLIENT; + } else if (isMainnet(network)) { throw new Error("Please use devnet or testnet for testing"); } else { throw new Error(`Unknown network: ${network}`); @@ -47,12 +53,14 @@ const aptosClient = (network?: string) => { } const faucet = (network?: string) => { - if (network === NetworkName.Devnet.toLowerCase()) { + if (isDevnet(network)) { return DEVNET_FAUCET; - } else if (network === NetworkName.Testnet.toLowerCase()) { + } else if (isTestnet(network)) { return TESTNET_FAUCET; - } else if (network === NetworkName.Mainnet.toLowerCase()) { + } else if (isMainnet(network)) { throw new Error("Please use devnet or testnet for testing"); + } else if (isLocal(network)) { + return LOCAL_FAUCET; } else { throw new Error(`Unknown network: ${network}`); } @@ -60,12 +68,33 @@ const faucet = (network?: string) => { const DEVNET_CLIENT = new Provider(Network.DEVNET); const TESTNET_CLIENT = new Provider(Network.TESTNET); +const LOCAL_CLIENT = new Provider(Network.LOCAL); const DEVNET_FAUCET = new FaucetClient("https://fullnode.devnet.aptoslabs.com", "https://faucet.devnet.aptoslabs.com"); const TESTNET_FAUCET = new FaucetClient("https://fullnode.testnet.aptoslabs.com", "https://faucet.testnet.aptoslabs.com"); +const LOCAL_FAUCET = new FaucetClient("http://localhost:8080", "http://localhost:8081"); + +const isDevnet = (network?: string): boolean => { + let net = network?.toLowerCase(); + return net === Network.DEVNET.toLowerCase() +} +const isTestnet = (network?: string): boolean => { + let net = network?.toLowerCase(); + return net === Network.TESTNET.toLowerCase() +} +const isMainnet = (network?: string): boolean => { + let net = network?.toLowerCase(); + return net === Network.MAINNET.toLowerCase() +} + +const isLocal = (network?: string): boolean => { + let net = network?.toLowerCase(); + return net === "localhost" || net === "local" +} const isSendableNetwork = (connected: boolean, network?: string): boolean => { - return connected && (network?.toLowerCase() === NetworkName.Devnet.toLowerCase() - || network?.toLowerCase() === NetworkName.Testnet.toLowerCase()) + return connected && (isDevnet(network) + || isTestnet(network) + || isLocal(network)) } export default function App() { @@ -312,12 +341,12 @@ function OptionalFunctionality() { connected, account, network, - prepMultiTransaction, signAndSubmitBCSTransaction, signTransaction, signMessageAndVerify, - signMultiAgentTransaction, - signAndSubmitMultiTransaction, + prepRawTransaction, + signRawTransaction, + signAndSubmitRawTransaction, } = useWallet(); let sendable = isSendableNetwork(connected, network?.name) @@ -427,7 +456,7 @@ function OptionalFunctionality() { }; // Get the information about gas & expiration from the wallet - let rawTxn = await prepMultiTransaction({ + let rawTxn = await prepRawTransaction({ payload: payload, feePayer: { publicKey: feePayerAccount.pubKey().hex(), @@ -441,14 +470,14 @@ function OptionalFunctionality() { } // Sign with fee payer - const feePayerAuthenticator = await provider.signMultiTransaction(feePayerAccount, rawTxn); + const feePayerAuthenticator = await provider.signMultiTransaction(feePayerAccount, (rawTxn as TxnBuilderTypes.FeePayerRawTransaction)); // Sign and submit with wallet - const response: undefined | Types.PendingTransaction = await signAndSubmitMultiTransaction({ + const response = await signAndSubmitRawTransaction({ rawTransaction: rawTxn, - feePayerSignature: HexString.fromUint8Array(feePayerAuthenticator.signature.value).hex() + feePayerAuthenticator: feePayerAuthenticator, }); - if (response?.hash === undefined) { + if (!response?.hash) { throw new Error(`No response given ${response}`) } await aptosClient(network?.name.toLowerCase()).waitForTransaction(response.hash); @@ -485,23 +514,21 @@ function OptionalFunctionality() { // We need to increase the default timeout let expiration_timestamp_secs = Math.floor(Date.now() / 1000) + 60000; - const rawTxn = await provider.generateFeePayerTransaction(account.address, payload, feePayerAccount.address().hex(), [], {expiration_timestamp_secs: expiration_timestamp_secs.toString()}) // Sign with fee payer const feePayerAuthenticator = await provider.signMultiTransaction(feePayerAccount, rawTxn); // Sign with user - const userSignature = await signMultiAgentTransaction(rawTxn); - // TODO: Why do we need to check this when the error should fail? + const userSignature = await signRawTransaction(rawTxn); if (!userSignature) { return; } // TODO: This extra code here needs to be put into the wallet adapter let userAuthenticator = new TxnBuilderTypes.AccountAuthenticatorEd25519( - new TxnBuilderTypes.Ed25519PublicKey(HexString.ensure(account.publicKey as string).toUint8Array()), - new TxnBuilderTypes.Ed25519Signature(HexString.ensure(userSignature as string).toUint8Array()) + new TxnBuilderTypes.Ed25519PublicKey(HexString.ensure(userSignature.publicKey as string).toUint8Array()), + new TxnBuilderTypes.Ed25519Signature(HexString.ensure(userSignature.signature as string).toUint8Array()) ); // Submit it TODO: the wallet possibly should send it instead? diff --git a/packages/wallet-adapter-core/src/WalletCore.ts b/packages/wallet-adapter-core/src/WalletCore.ts index 1f6fd488..00dbd3c8 100644 --- a/packages/wallet-adapter-core/src/WalletCore.ts +++ b/packages/wallet-adapter-core/src/WalletCore.ts @@ -1,4 +1,5 @@ import { + AnyRawTransaction, HexString, Network, Provider, @@ -35,7 +36,7 @@ import { Wallet, WalletInfo, WalletCoreEvents, - TransactionOptions, + TransactionOptions, RawTransactionRequest, RawTransactionPrepPayload, SignRawTransactionResponse, } from "./types"; import { removeLocalStorage, @@ -377,27 +378,6 @@ export class WalletCore extends EventEmitter { } } - async signMultiAgentTransaction( - transaction: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction - ): Promise { - if (this._wallet && !("signMultiAgentTransaction" in this._wallet)) { - throw new WalletNotSupportedMethod( - `Multi agent & Fee payer transactions are not supported by ${this.wallet?.name}` - ).message; - } - try { - this.doesWalletExist(); - const response = await (this._wallet as any).signMultiAgentTransaction( - transaction - ); - return response; - } catch (error: any) { - const errMsg = - typeof error == "object" && "message" in error ? error.message : error; - throw new WalletSignTransactionError(errMsg).message; - } - } - /** Event for when account has changed on the wallet @return the new account info @@ -508,7 +488,7 @@ export class WalletCore extends EventEmitter { } } - async prepMultiTransaction(input: MultiTransactionPrepPayload): Promise { + async prepRawTransaction(input: RawTransactionPrepPayload): Promise { try { this.doesWalletExist(); } catch (error: any) { @@ -524,33 +504,52 @@ export class WalletCore extends EventEmitter { // If there is a fee payer, then it's a fee payer transaction // Default expiration time is 60 seconds because there are more actions - const expiration_timestamp_secs = Math.floor(Date.now() / 1000) + (input.expirationMilliseconds ?? 60000); + const expiration_timestamp_secs = Math.floor(Date.now() / 1000) + (input.options?.expiration_milliseconds ?? 60000); const additional_signers = input.additionalSigners?.map((value) => {return value.address}) ?? []; - // Fee payer has the fee payer, otherwise it's multiagent // TODO: Add simulation for gas estimation - if(input.feePayer) { + if (!input.feePayer && !input.additionalSigners) { + return await provider.generateTransaction(HexString.ensure(this._account.address), input.payload) + } else if (input.feePayer) { return await provider.generateFeePayerTransaction(this._account.address, input.payload, input.feePayer.address, additional_signers, { max_gas_amount: input.options?.max_gas_amount?.toString(), gas_unit_price: input.options?.gas_unit_price?.toString(), expiration_timestamp_secs: expiration_timestamp_secs.toString() }) } else { - // TODO: Support multiagent + // TODO: Support multiagent without fee payer throw new WalletNotSupportedMethod(`Multiagent without fee payer is not supported yet`).message; } } - /** - * - */ - async signAndSubmitMultiTransaction(input: MultiTransactionSubmissionPayload): Promise { - if (this._wallet && !("signAndSubmitMultiTransaction" in this._wallet)) { - throw new WalletNotSupportedMethod(`SignAndSubmitMultiTransaction not supported by ${this.wallet?.name}`).message; + async signRawTransaction( + rawTransaction: AnyRawTransaction + ): Promise { + if (this._wallet && !("signRawTransaction" in this._wallet)) { + throw new WalletNotSupportedMethod( + `SignRawTransaction is not supported by ${this.wallet?.name}` + ).message; } try { this.doesWalletExist(); - return await (this._wallet as any).signAndSubmitMultiTransaction(input); + const response = await (this._wallet as any).signRawTransaction( + rawTransaction + ); + return response; + } catch (error: any) { + const errMsg = + typeof error == "object" && "message" in error ? error.message : error; + throw new WalletSignTransactionError(errMsg).message; + } + } + + async signAndSubmitRawTransaction(input: RawTransactionRequest): Promise { + if (this._wallet && !("signAndSubmitRawTransaction" in this._wallet)) { + throw new WalletNotSupportedMethod(`signAndSubmitRawTransaction not supported by ${this.wallet?.name}`).message; + } + try { + this.doesWalletExist(); + return await (this._wallet as any).signAndSubmitRawTransaction(input); } catch (error: any) { throw this.convertError(error); } @@ -598,23 +597,3 @@ export class WalletCore extends EventEmitter { return new WalletSignTransactionError(errMsg).message; } } - -export interface MultiTransactionSubmissionPayload { - rawTransaction: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction, - feePayerSignature?: string, - additionalSignatures?: string[], - options?: TransactionOptions -} - -export interface MultiTransactionPrepPayload { - payload: Types.EntryFunctionPayload, - feePayer?: SignerIdentity, - additionalSigners?: SignerIdentity[], - options?: TransactionOptions - expirationMilliseconds?: number -} - -export interface SignerIdentity { - publicKey: string, - address: string, -} diff --git a/packages/wallet-adapter-core/src/types.ts b/packages/wallet-adapter-core/src/types.ts index 3b598bee..a98ae213 100644 --- a/packages/wallet-adapter-core/src/types.ts +++ b/packages/wallet-adapter-core/src/types.ts @@ -1,6 +1,5 @@ -import { TxnBuilderTypes, Types } from "aptos"; +import {TxnBuilderTypes, Types} from "aptos"; import { NetworkName, WalletReadyState } from "./constants"; -import {MultiTransactionPrepPayload, MultiTransactionSubmissionPayload} from "./WalletCore"; export { TxnBuilderTypes, Types } from "aptos"; // WalletName is a nominal type that wallet adapters should use, e.g. `'MyCryptoWallet' as WalletName<'MyCryptoWallet'>` @@ -44,12 +43,10 @@ export interface PluginProvider { listener: (newAddress: AccountInfo) => Promise ) => Promise; onNetworkChange: OnNetworkChange; - signMultiAgentTransaction: ( - rawTxn: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction - ) => Promise; - - prepMultiTransaction: (input: MultiTransactionPrepPayload) => Promise; - signAndSubmitMultiTransaction: (input: MultiTransactionSubmissionPayload) => Promise; + signRawTransaction: ( + rawTransaction: AnyRawTransaction + ) => Promise; + signAndSubmitRawTransaction: (input: RawTransactionRequest) => Promise; } export interface AdapterPluginEvents { @@ -121,3 +118,41 @@ export interface TransactionOptions { max_gas_amount?: bigint; gas_unit_price?: bigint; } + +// TODO: Support MultiEd25519? +export interface SignRawTransactionResponse { + signature: string; + publicKey: string; +} + + + +export interface SignAndSubmitRawTransactionResponse { + hash: string +} + +export interface RawTransactionOptions { + max_gas_amount?: bigint; + gas_unit_price?: bigint; + expiration_milliseconds?: number; +} + +export type AnyRawTransaction = TxnBuilderTypes.RawTransaction | TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction; + +export interface RawTransactionRequest { + rawTransaction: AnyRawTransaction, + feePayerAuthenticator?: TxnBuilderTypes.AccountAuthenticator, + additionalAuthenticators?: TxnBuilderTypes.AccountAuthenticator, +} + +export interface RawTransactionPrepPayload { + payload: Types.EntryFunctionPayload, + feePayer?: SignerIdentity, + additionalSigners?: SignerIdentity[], + options?: RawTransactionOptions +} + +export interface SignerIdentity { + publicKey: string, + address: string, +} diff --git a/packages/wallet-adapter-react/src/WalletProvider.tsx b/packages/wallet-adapter-react/src/WalletProvider.tsx index 1819b350..13507534 100644 --- a/packages/wallet-adapter-react/src/WalletProvider.tsx +++ b/packages/wallet-adapter-react/src/WalletProvider.tsx @@ -16,13 +16,10 @@ import type { WalletName, TransactionOptions, TxnBuilderTypes, - Types, + Types, RawTransactionPrepPayload, RawTransactionRequest, } from "@aptos-labs/wallet-adapter-core"; import { WalletCore } from "@aptos-labs/wallet-adapter-core"; -import { - MultiTransactionPrepPayload, - MultiTransactionSubmissionPayload -} from "@aptos-labs/wallet-adapter-core/src/WalletCore"; +import {AnyRawTransaction} from "@aptos-labs/wallet-adapter-core/src"; export interface AptosWalletProviderProps { children: ReactNode; @@ -139,11 +136,11 @@ export const AptosWalletAdapterProvider: FC = ({ }; - const signMultiAgentTransaction = async ( - transaction: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction, + const signRawTransaction = async ( + transaction: AnyRawTransaction, ) => { try { - return await walletCore.signMultiAgentTransaction(transaction); + return await walletCore.signRawTransaction(transaction); } catch (error: any) { if (onError) onError(error); else throw error; @@ -151,23 +148,18 @@ export const AptosWalletAdapterProvider: FC = ({ } } - const prepMultiTransaction = async ( - input: MultiTransactionPrepPayload + const prepRawTransaction = async ( + input: RawTransactionPrepPayload ) => { - try { - return await walletCore.prepMultiTransaction(input); - } catch (error: any) { - if (onError) onError(error); - else throw error; - return null; - } + // TODO: Probably move to SDK + return await walletCore.prepRawTransaction(input); } - const signAndSubmitMultiTransaction = async ( - input: MultiTransactionSubmissionPayload, + const signAndSubmitRawTransaction = async ( + input: RawTransactionRequest, ) => { try { - return await walletCore.signAndSubmitMultiTransaction(input); + return await walletCore.signAndSubmitRawTransaction(input); } catch (error: any) { if (onError) onError(error); else throw error; @@ -281,9 +273,9 @@ export const AptosWalletAdapterProvider: FC = ({ signTransaction, signMessage, signMessageAndVerify, - signMultiAgentTransaction, - signAndSubmitMultiTransaction, - prepMultiTransaction, + signRawTransaction, + signAndSubmitRawTransaction, + prepRawTransaction, isLoading, }} > diff --git a/packages/wallet-adapter-react/src/useWallet.tsx b/packages/wallet-adapter-react/src/useWallet.tsx index 021880f6..aec2c87e 100644 --- a/packages/wallet-adapter-react/src/useWallet.tsx +++ b/packages/wallet-adapter-react/src/useWallet.tsx @@ -13,13 +13,14 @@ import { isMobile, TransactionOptions, TxnBuilderTypes, - Types, + Types, RawTransactionPrepPayload, RawTransactionRequest, } from "@aptos-labs/wallet-adapter-core"; import { createContext, useContext } from "react"; import { - MultiTransactionPrepPayload, - MultiTransactionSubmissionPayload -} from "@aptos-labs/wallet-adapter-core/src/WalletCore"; + AnyRawTransaction, + SignAndSubmitRawTransactionResponse, + SignRawTransactionResponse +} from "@aptos-labs/wallet-adapter-core/src"; export type { WalletName, Wallet }; export { @@ -54,12 +55,11 @@ export interface WalletContextState { signMessage(message: SignMessagePayload): Promise; signMessageAndVerify(message: SignMessagePayload): Promise; - signMultiAgentTransaction( - transaction: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction, - ): Promise; - - prepMultiTransaction(input: MultiTransactionPrepPayload): Promise; - signAndSubmitMultiTransaction(input: MultiTransactionSubmissionPayload): Promise; + signRawTransaction( + transaction: AnyRawTransaction, + ): Promise; + prepRawTransaction(input: RawTransactionPrepPayload): Promise; + signAndSubmitRawTransaction(input: RawTransactionRequest): Promise; } const DEFAULT_COUNTEXT = {