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 prepareForSend for EVM and SVM connector #477

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions .changeset/spotty-camels-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@fuel-connectors/burner-wallet-connector": minor
"@fuel-connectors/walletconnect-connector": minor
"@fuel-connectors/solana-connector": minor
"@fuel-connectors/evm-connector": minor
---

Add compatibility with PreparedTransaction
2 changes: 1 addition & 1 deletion examples/react-app/src/components/transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function Transfer({ isSigning, setIsSigning }: Props) {
};
} catch (err) {
const error = err as CustomError;
console.error(error.message);
console.error(error);

setToast({
open: true,
Expand Down
22 changes: 22 additions & 0 deletions packages/burner-wallet-connector/src/BurnerWalletConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export class BurnerWalletConnector extends FuelConnector {
},
};

usePrepareForSend = true;

private burnerWallet: WalletUnlocked | null = null;
private fuelProvider: Provider | null = null;
private storage: StorageAbstract | Storage;
Expand Down Expand Up @@ -230,6 +232,26 @@ export class BurnerWalletConnector extends FuelConnector {
return transactionRequest.id;
}

public async prepareForSend(
address: string,
transaction: TransactionRequestLike,
): Promise<TransactionRequestLike> {
if (!this.burnerWallet) {
throw Error('Wallet not connected');
}

if (address !== this.burnerWallet.address.toString()) {
throw Error('Address not found for the connector');
}

const signedTransactionRequest =
await this.burnerWallet.populateTransactionWitnessesSignature(
transaction,
);

return signedTransactionRequest;
}

async currentAccount(): Promise<string | null> {
if (!this.burnerWallet) {
throw Error('Wallet not connected');
Expand Down
43 changes: 37 additions & 6 deletions packages/evm-connector/src/EvmWalletConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
PredicateConnector,
type PredicateVersion,
type PredicateWalletAdapter,
type PreparedTransaction,
type ProviderDictionary,
getMockedSignatureIndex,
getOrThrow,
Expand Down Expand Up @@ -56,6 +57,7 @@ export class EVMWalletConnector extends PredicateConnector {
private _currentAccount: string | null = null;
private config: EVMWalletConnectorConfig = {};
private _ethereumEvents = 0;
usePrepareForSend = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

@petertonysmith94 @danielbate
makes sense to the need for this flag? the sdk could just verify if prepareForSend method exists

Copy link
Contributor

Choose a reason for hiding this comment

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

The prepareForSend method is on the abstract FuelConnector class, and therefore, this method will always exist. Open to suggestions if you have another solution for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

it can't be optional?

Copy link
Contributor

Choose a reason for hiding this comment

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

As it's an abstract method on the abstract class, it can't be optional. We could introduce a new interface that the underlying connector would implement, however, this could lead to unexpected behaviours with the Fuel SDK.

interface PrepareForSendConnector {
  prepareForSend(
    address: string,
    transaction: TransactionRequestLike
  ): Promise<TransactionRequestLike>;
}

IMO the flag is simple and explicit.

Copy link
Contributor

Choose a reason for hiding this comment

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

it can't be an optional method in the abstract class?


constructor(config: EVMWalletConnectorConfig = {}) {
super();
Expand Down Expand Up @@ -230,12 +232,12 @@ export class EVMWalletConnector extends PredicateConnector {
return false;
}

public async sendTransaction(
private async prepareAndSignTransaction(
address: string,
transaction: TransactionRequestLike,
): Promise<string> {
): Promise<PreparedTransaction> {
const { ethProvider, fuelProvider } = await this.getProviders();
const { request, transactionId, account, transactionRequest } =
const { request, transactionId, account, transactionRequest, predicate } =
await this.prepareTransaction(address, transaction);

const txId = this.encodeTxId(transactionId);
Expand All @@ -255,15 +257,44 @@ export class EVMWalletConnector extends PredicateConnector {
const transactionWithPredicateEstimated =
await fuelProvider.estimatePredicates(request);

return {
predicate,
transactionId,
transactionRequest: transactionWithPredicateEstimated,
request,
account: address,
};
}

public async sendTransaction(
address: string,
transaction: TransactionRequestLike,
): Promise<string> {
const { fuelProvider } = await this.getProviders();
const { transactionRequest } = await this.prepareAndSignTransaction(
address,
transaction,
);

const response = await fuelProvider.operations.submit({
encodedTransaction: hexlify(
transactionWithPredicateEstimated.toTransactionBytes(),
),
encodedTransaction: hexlify(transactionRequest.toTransactionBytes()),
});

return response.submit.id;
}

public async prepareForSend(
address: string,
transaction: TransactionRequestLike,
): Promise<TransactionRequestLike> {
const { transactionRequest } = await this.prepareAndSignTransaction(
address,
transaction,
);

return transactionRequest;
}

async signMessageCustomCurve(message: string) {
const { ethProvider } = await this.getProviders();
if (!ethProvider) throw new Error('Eth provider not found');
Expand Down
39 changes: 34 additions & 5 deletions packages/solana-connector/src/SolanaConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
PredicateConnector,
type PredicateVersion,
type PredicateWalletAdapter,
type PreparedTransaction,
type ProviderDictionary,
SolanaWalletAdapter,
getFuelPredicateAddresses,
Expand Down Expand Up @@ -38,6 +39,7 @@ export class SolanaConnector extends PredicateConnector {
link: 'https://solana.com/ecosystem/explore?categories=wallet',
},
};
usePrepareForSend = true;

protected fuelProvider!: FuelProvider;

Expand Down Expand Up @@ -230,14 +232,38 @@ export class SolanaConnector extends PredicateConnector {
address: string,
transaction: TransactionRequestLike,
): Promise<string> {
const { predicate, transactionRequest } =
await this.prepareAndSignTransaction(address, transaction);

const response = await predicate.sendTransaction(transactionRequest);

return response.id;
}

public async prepareForSend(
address: string,
transaction: TransactionRequestLike,
): Promise<TransactionRequestLike> {
const { transactionRequest } = await this.prepareAndSignTransaction(
address,
transaction,
);

return transactionRequest;
}

private async prepareAndSignTransaction(
address: string,
transaction: TransactionRequestLike,
): Promise<PreparedTransaction> {
const { predicate, transactionId, transactionRequest } =
await this.prepareTransaction(address, transaction);

const predicateSignatureIndex = getMockedSignatureIndex(
transactionRequest.witnesses,
);

const txId = this.encodeTxId(transactionId);
const txId = await this.encodeTxId(transactionId);
const provider: Maybe<SolanaProvider> =
this.web3Modal.getWalletProvider() as SolanaProvider;
if (!provider) {
Expand All @@ -249,12 +275,15 @@ export class SolanaConnector extends PredicateConnector {
)) as Uint8Array;
transactionRequest.witnesses[predicateSignatureIndex] = signedMessage;

// Send transaction
await predicate.provider.estimatePredicates(transactionRequest);

const response = await predicate.sendTransaction(transactionRequest);

return response.id;
return {
predicate,
transactionId,
transactionRequest,
request: transactionRequest,
account: address,
};
}

async signMessageCustomCurve(message: string) {
Expand Down
76 changes: 40 additions & 36 deletions packages/walletconnect-connector/src/WalletConnectConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
CHAIN_IDS,
type ConnectorMetadata,
FuelConnectorEventTypes,
type FuelConnectorSendTxParams,
Provider as FuelProvider,
LocalStorage,
type StorageAbstract,
Expand All @@ -33,6 +32,7 @@ import {
PredicateConnector,
type PredicateVersion,
type PredicateWalletAdapter,
type PreparedTransaction,
type ProviderDictionary,
getFuelPredicateAddresses,
getMockedSignatureIndex,
Expand All @@ -45,7 +45,7 @@ import {
txIdEncoders,
} from '@fuel-connectors/evm-predicates';
import { ApiController } from '@web3modal/core';
import { type TransactionRequest, stringToHex } from 'viem';
import { stringToHex } from 'viem';
import {
ETHEREUM_ICON,
HAS_WINDOW,
Expand All @@ -68,6 +68,7 @@ export class WalletConnectConnector extends PredicateConnector {
link: 'https://ethereum.org/en/wallets/find-wallet/',
},
};
usePrepareForSend = true;

private fuelProvider!: FuelProvider;
private ethProvider!: EIP1193Provider;
Expand Down Expand Up @@ -407,25 +408,40 @@ export class WalletConnectConnector extends PredicateConnector {
public async sendTransaction(
address: string,
transaction: TransactionRequestLike,
params?: FuelConnectorSendTxParams,
): Promise<string> {
const { ethProvider, fuelProvider } = await this.getProviders();
console.log('asd inputs BEFORE prepare', transaction.inputs?.toString());
console.log(
'asd witnesses BEFORE prepare',
transaction.witnesses?.toString(),
);
const { request, transactionId, account, transactionRequest } =
await this.prepareTransaction(address, transaction);
console.log(
'asd inputs AFTER prepare',
transactionRequest.inputs?.toString(),
const { fuelProvider } = await this.getProviders();
const { transactionRequest } = await this.prepareAndSignTransaction(
address,
transaction,
);
console.log(
'asd witnesses AFTER prepare',
transactionRequest.witnesses?.toString(),

const response = await fuelProvider.operations.submit({
encodedTransaction: hexlify(transactionRequest.toTransactionBytes()),
});

return response.submit.id;
}

public async prepareForSend(
address: string,
transaction: TransactionRequestLike,
): Promise<TransactionRequestLike> {
const { transactionRequest } = await this.prepareAndSignTransaction(
address,
transaction,
);

return transactionRequest;
}

private async prepareAndSignTransaction(
address: string,
transaction: TransactionRequestLike,
): Promise<PreparedTransaction> {
const { ethProvider, fuelProvider } = await this.getProviders();
const { request, transactionId, account, transactionRequest, predicate } =
await this.prepareTransaction(address, transaction);

const txId = this.encodeTxId(transactionId);
const signature = (await ethProvider?.request({
method: 'personal_sign',
Expand All @@ -438,30 +454,18 @@ export class WalletConnectConnector extends PredicateConnector {

// Transform the signature into compact form for Sway to understand
const compactSignature = splitSignature(hexToBytes(signature)).compact;

console.log('asd predicateSignatureIndex', predicateSignatureIndex);
console.log(
'asd transactionRequest.witnesses',
transactionRequest.witnesses?.toString(),
);
console.log('asd compactSignature', compactSignature);
transactionRequest.witnesses[predicateSignatureIndex] = compactSignature;

const transactionWithPredicateEstimated =
await fuelProvider.estimatePredicates(request);

let txAfterUserCallback = transactionWithPredicateEstimated;
if (params?.onBeforeSend) {
txAfterUserCallback = await params.onBeforeSend(
transactionWithPredicateEstimated,
);
}

const response = await fuelProvider.operations.submit({
encodedTransaction: hexlify(txAfterUserCallback.toTransactionBytes()),
});

return response.submit.id;
return {
predicate,
transactionId,
transactionRequest: transactionWithPredicateEstimated,
request,
account: address,
};
}

private isValidPredicateAddress(
Expand Down
Loading