Skip to content

Commit

Permalink
[fee-payer] Cleanup multi-agent APIs to now just be for raw transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
gregnazario committed Oct 30, 2023
1 parent e3b2dce commit cb6a025
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 117 deletions.
71 changes: 49 additions & 22 deletions apps/nextjs-example/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -35,37 +39,62 @@ 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}`);
}
}

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}`);
}
}

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() {
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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(),
Expand All @@ -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);
Expand Down Expand Up @@ -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?
Expand Down
87 changes: 33 additions & 54 deletions packages/wallet-adapter-core/src/WalletCore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AnyRawTransaction,
HexString,
Network,
Provider,
Expand Down Expand Up @@ -35,7 +36,7 @@ import {
Wallet,
WalletInfo,
WalletCoreEvents,
TransactionOptions,
TransactionOptions, RawTransactionRequest, RawTransactionPrepPayload, SignRawTransactionResponse,
} from "./types";
import {
removeLocalStorage,
Expand Down Expand Up @@ -377,27 +378,6 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
}
}

async signMultiAgentTransaction(
transaction: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction
): Promise<string | null> {
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
Expand Down Expand Up @@ -508,7 +488,7 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
}
}

async prepMultiTransaction(input: MultiTransactionPrepPayload): Promise<TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction | null> {
async prepRawTransaction(input: RawTransactionPrepPayload): Promise<AnyRawTransaction> {
try {
this.doesWalletExist();
} catch (error: any) {
Expand All @@ -524,33 +504,52 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {

// 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<any> {
if (this._wallet && !("signAndSubmitMultiTransaction" in this._wallet)) {
throw new WalletNotSupportedMethod(`SignAndSubmitMultiTransaction not supported by ${this.wallet?.name}`).message;
async signRawTransaction(
rawTransaction: AnyRawTransaction
): Promise<SignRawTransactionResponse | null> {
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<any> {
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);
}
Expand Down Expand Up @@ -598,23 +597,3 @@ export class WalletCore extends EventEmitter<WalletCoreEvents> {
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,
}
51 changes: 43 additions & 8 deletions packages/wallet-adapter-core/src/types.ts
Original file line number Diff line number Diff line change
@@ -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'>`
Expand Down Expand Up @@ -44,12 +43,10 @@ export interface PluginProvider {
listener: (newAddress: AccountInfo) => Promise<void>
) => Promise<void>;
onNetworkChange: OnNetworkChange;
signMultiAgentTransaction: (
rawTxn: TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction
) => Promise<string>;

prepMultiTransaction: (input: MultiTransactionPrepPayload) => Promise<TxnBuilderTypes.MultiAgentRawTransaction | TxnBuilderTypes.FeePayerRawTransaction | null>;
signAndSubmitMultiTransaction: (input: MultiTransactionSubmissionPayload) => Promise<any>;
signRawTransaction: (
rawTransaction: AnyRawTransaction
) => Promise<SignRawTransactionResponse | null>;
signAndSubmitRawTransaction: (input: RawTransactionRequest) => Promise<SignAndSubmitRawTransactionResponse | null>;
}

export interface AdapterPluginEvents {
Expand Down Expand Up @@ -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,
}
Loading

0 comments on commit cb6a025

Please sign in to comment.