Skip to content

Commit de8555b

Browse files
committed
Add reject transaction functionality in MSafeAccountList component, allowing users to reject multi-signature transactions. Update transaction handling to include expirationRaw and enhance transaction submission logic with improved error handling.
1 parent ff6f6f2 commit de8555b

File tree

3 files changed

+127
-5
lines changed

3 files changed

+127
-5
lines changed

src/components/msafe-account-list.tsx

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Aptos, AptosConfig, Deserializer, Ed25519PublicKey, Ed25519Signature, N
44
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
55
import { Button } from "@/components/ui/button"
66
import { computeMultiSigAddress, toHex } from "@/utils/signature"
7-
import { makeEntryFunctionTx, makeInitTx, type EntryFunctionArgs, type IMultiSig, type IAccount, assembleMultiSig, assembleMultiSigTxn, makeSubmitSignatureTxn } from "@/utils/msafe-txn"
7+
import { makeEntryFunctionTx, makeInitTx, type EntryFunctionArgs, type IMultiSig, type IAccount, assembleMultiSig, assembleMultiSigTxn, makeSubmitSignatureTxn, makeMSafeRevertTx } from "@/utils/msafe-txn"
88
import { WalletConnectors } from "msafe-wallet-adaptor"
99
import { BCS, TxnBuilderTypes, HexString } from "aptos"
1010

@@ -983,6 +983,90 @@ export function MSafeAccountList({ onAccountSelect }: MSafeAccountListProps) {
983983
}
984984
}, [selectedAccount, account, getSequenceNumber, loadWithdrawalRequests, signAndSubmitTransaction, aptos.transaction])
985985

986+
const rejectMsafeTransaction = useCallback(async (msafeTransaction: MSafeTxnInfo) => {
987+
console.log('Rejecting msafe transactions:', msafeTransaction)
988+
if (!selectedAccount || !account?.address) return
989+
try {
990+
const msafeAccount = await WalletConnectors['Pontem']();
991+
const msafeAccountInterface: IMultiSig = {
992+
address: new HexString(selectedAccount.address),
993+
}
994+
const rejectTransaction = await makeMSafeRevertTx(
995+
msafeAccountInterface,
996+
{ sn: msafeTransaction.sn },
997+
{
998+
maxGas: msafeTransaction.maxGas,
999+
gasPrice: msafeTransaction.gasPrice,
1000+
expirationRaw: msafeTransaction.expirationRaw, // expiration
1001+
chainID: msafeTransaction.chainID, // mainnet,
1002+
sequenceNumber: msafeTransaction.sn,
1003+
estimateGasPrice: true,
1004+
estimateMaxGas: true
1005+
}
1006+
)
1007+
console.log('Reject transaction:', rejectTransaction)
1008+
const [p, s] = await msafeAccount.getSigData(rejectTransaction.raw)
1009+
console.log('Payload:', p)
1010+
console.log('Signature:', s)
1011+
const msafeInfo = await getMSafeInfo(selectedAccount.address)
1012+
if (!msafeInfo) {
1013+
throw new Error('Failed to get MSafe information')
1014+
}
1015+
// Find the index of the current account's public key
1016+
const currentAccountPubKey = account.publicKey?.toString() || ''
1017+
const pkIndex = msafeInfo.public_keys.findIndex((pk) =>
1018+
isHexEqual(pk, currentAccountPubKey)
1019+
)
1020+
1021+
if (pkIndex === -1) {
1022+
throw new Error('Current account is not an owner of this MSafe')
1023+
}
1024+
1025+
console.log('Found public key index:', pkIndex, 'for public key:', currentAccountPubKey)
1026+
const signerAccount: IAccount = {
1027+
address: new HexString(account.address.toString()),
1028+
publicKey: () => {
1029+
if (!account.publicKey) {
1030+
throw new Error("Public key not available from wallet");
1031+
}
1032+
// Convert the public key string to Ed25519PublicKey
1033+
const publicKeyBytes = new HexString(account.publicKey.toString()).toUint8Array();
1034+
return new TxnBuilderTypes.Ed25519PublicKey(publicKeyBytes);
1035+
}
1036+
};
1037+
const submitSignatureTx = await makeSubmitSignatureTxn(
1038+
signerAccount,
1039+
msafeTransaction.hash.hex(),
1040+
pkIndex,
1041+
new HexString(selectedAccount.address),
1042+
s,
1043+
{
1044+
maxGas: 100000n,
1045+
gasPrice: 100n,
1046+
expirationSec: 30, // 30 seconds
1047+
chainID: 1, // mainnet,
1048+
sequenceNumber: await getAccountSequenceNumber(account.address.toString()),
1049+
estimateGasPrice: true,
1050+
estimateMaxGas: true
1051+
}
1052+
)
1053+
1054+
const signedTx = await msafeAccount.sign(submitSignatureTx.raw)
1055+
const aptosClient = new (await import('aptos')).AptosClient('https://fullnode.mainnet.aptoslabs.com');
1056+
const txRes = await aptosClient.submitSignedBCSTransaction(signedTx)
1057+
console.log('Transaction submitted:', txRes)
1058+
const committedTx = await aptos.transaction.waitForTransaction({
1059+
transactionHash: txRes.hash
1060+
})
1061+
console.log('Committed transaction:', committedTx)
1062+
await loadWithdrawalRequests()
1063+
await checkRegistration();
1064+
return;
1065+
} catch (error) {
1066+
console.error('Failed to reject msafe transaction:', error)
1067+
}
1068+
}, [selectedAccount, account, loadWithdrawalRequests, checkRegistration, aptos.transaction, getMSafeInfo, getAccountSequenceNumber])
1069+
9861070
// Load balances for selected account
9871071
const loadAccountBalances = useCallback(async (account: MSafeAccount) => {
9881072
if (account.isLoadingBalances) return
@@ -1351,6 +1435,7 @@ export function MSafeAccountList({ onAccountSelect }: MSafeAccountListProps) {
13511435
const token = request.fa_metadata?.inner;
13521436
const tokenData = getTokenData(token);
13531437
const amount = request.amount ? parseFloat(request.amount) / Math.pow(10, tokenData?.decimals || 8) : 0;
1438+
const isExecuted = request.status?.__variant__ === 'Executed'
13541439

13551440
// Check if there's a pending transaction for this request
13561441
let hasPendingTx = false
@@ -1410,7 +1495,7 @@ export function MSafeAccountList({ onAccountSelect }: MSafeAccountListProps) {
14101495
<b>Sequence Number:</b> {msafeTransactions.map(tx => tx.sn).join(', ')}
14111496
</div>
14121497
)}
1413-
{(hasPayload && !hasPendingTx && !pendingTxInfo) && (
1498+
{(hasPayload && !hasPendingTx && !pendingTxInfo && !isExecuted) && (
14141499
<LoadingButton
14151500
size="sm"
14161501
loading={isSigning}
@@ -1548,7 +1633,6 @@ export function MSafeAccountList({ onAccountSelect }: MSafeAccountListProps) {
15481633
{transactions.map((tx, txIndex) => {
15491634
const threshold = registryData?.threshold.get(tx.sender.hex()) || 'N/A'
15501635
const isExpired = tx.expiration.getTime() < new Date().getTime()
1551-
console.log('tx', tx)
15521636

15531637
return (
15541638
<tr key={`${sn}-${txIndex}`} className={`border-b ${isExpired ? 'bg-red-50 dark:bg-red-950/20' : ''}`}>
@@ -1603,9 +1687,21 @@ export function MSafeAccountList({ onAccountSelect }: MSafeAccountListProps) {
16031687
>
16041688
{tx.isSigned ? 'Completed' : isExpired ? 'Expired' : 'Sign & Send'}
16051689
</LoadingButton>
1690+
{!isExpired ? (
1691+
<LoadingButton
1692+
loading={false}
1693+
size="sm"
1694+
onClick={() => rejectMsafeTransaction(tx)}
1695+
disabled={tx.isSigned}
1696+
className="text-xs"
1697+
>
1698+
Reject
1699+
</LoadingButton>
1700+
) : (
16061701
<span className="text-xs text-red-600 dark:text-red-400 text-center">
16071702
Exp: {tx.expiration.toLocaleString()}
16081703
</span>
1704+
)}
16091705
</div>
16101706
</td>
16111707
</tr>

src/utils/msafe-txn.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HexString, TxnBuilderTypes, BCS } from "aptos";
2-
import { HexBuffer, Transaction as MTransaction, type TEd25519Signature, type SimpleMap, type TEd25519PublicKey } from "./transaction";
2+
import { HexBuffer, Transaction as MTransaction, type TEd25519Signature, type SimpleMap, type TEd25519PublicKey, type RevertArgs } from "./transaction";
33
import type { Account } from "node_modules/@aptos-labs/ts-sdk/dist/esm/api/account.d.mts";
44
import { isHexEqual } from "@/components/msafe-account-list";
55
import type { AccountAddress } from "@aptos-labs/ts-sdk";
@@ -56,6 +56,7 @@ export type Options = {
5656
maxGas?: bigint;
5757
gasPrice?: bigint;
5858
expirationSec?: number; // target = time.now() + expiration
59+
expirationRaw?: number;
5960
sequenceNumber?: bigint;
6061
chainID?: number;
6162
estimateGasPrice?: boolean;
@@ -66,6 +67,7 @@ export type TxConfig = {
6667
maxGas: bigint;
6768
gasPrice: bigint;
6869
expirationSec: number; // target = time.now() + expiration
70+
expirationRaw?: number;
6971
sequenceNumber: bigint;
7072
chainID: number;
7173
estimateGasPrice: boolean;
@@ -211,7 +213,7 @@ export class AptosEntryTxnBuilder {
211213
payload,
212214
this._config.maxGas,
213215
this._config.gasPrice,
214-
BigInt(Math.floor(Date.now() / 1000) + this._config.expirationSec),
216+
this._config.expirationRaw ? BigInt(this._config.expirationRaw) : BigInt(Math.floor(Date.now() / 1000) + this._config.expirationSec),
215217
new TxnBuilderTypes.ChainId(this._config.chainID)
216218
);
217219

@@ -282,6 +284,7 @@ export async function applyDefaultOptions(
282284
chainID: chainID,
283285
estimateGasPrice: !!opts.estimateGasPrice,
284286
estimateMaxGas: !!opts.estimateMaxGas,
287+
expirationRaw: opts.expirationRaw,
285288
};
286289
}
287290

@@ -364,6 +367,26 @@ export async function makeSubmitSignatureTxn(
364367
.build(signer);
365368
}
366369

370+
export async function makeMSafeRevertTx(
371+
sender: IMultiSig,
372+
args: RevertArgs,
373+
opts?: Options
374+
): Promise<MSafeTransaction> {
375+
const config = await applyDefaultOptions(sender.address, opts);
376+
// sequence number will override option sn
377+
config.sequenceNumber = args.sn;
378+
const txBuilder = new AptosEntryTxnBuilder();
379+
const tx = await txBuilder
380+
.addr(MSAFE_MODULES_ACCOUNT)
381+
.module(MODULES.MOMENTUM_SAFE)
382+
.method(FUNCTIONS.MSAFE_REVERT)
383+
.from(sender.address)
384+
.withTxConfig(config)
385+
.args([])
386+
.build(sender);
387+
return new MSafeTransaction(tx.raw);
388+
}
389+
367390
/**
368391
* Creates an MSafe init_transaction transaction
369392
* @param signer - The account that will sign and submit the init transaction

src/utils/transaction.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ export class SignedMessage<T extends Serializable> {
277277
sender: HexString;
278278
sn: bigint;
279279
expiration: Date;
280+
expirationRaw: number;
280281
chainID: number;
281282
gasPrice: bigint;
282283
maxGas: bigint;
@@ -308,6 +309,7 @@ export type TEd25519Signature = Types.Ed25519Signature['signature']
308309
hash: sha3_256(message.raw.toBytes()),
309310
chainID: migrationMessage.chain_id.value,
310311
expiration: new Date(),
312+
expirationRaw: new Date().getTime(),
311313
sender: HexString.ensure(migrationMessage.account_address.toHexString()),
312314
gasPrice: 0n,
313315
maxGas: 0n,
@@ -424,6 +426,7 @@ function decodeTypeTag(tArg: TxnBuilderTypes.TypeTag): string {
424426
sender: HexString.fromUint8Array(tx.sender.address),
425427
sn: tx.sequence_number,
426428
expiration: secToDate(tx.expiration_timestamp_secs),
429+
expirationRaw: Number(tx.expiration_timestamp_secs),
427430
chainID: tx.chain_id.value,
428431
gasPrice: tx.gas_unit_price,
429432
maxGas: tx.max_gas_amount,

0 commit comments

Comments
 (0)