Skip to content

Commit

Permalink
Merge pull request #68 from deep-ink-ventures/create-txn-flow-fix
Browse files Browse the repository at this point in the history
Create Transaction Fix
  • Loading branch information
GHesericsu authored Oct 13, 2023
2 parents ce14aa9 + cf9917c commit e3ece79
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 95 deletions.
57 changes: 32 additions & 25 deletions src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,18 @@ const Settings = (props: { accountId: string }) => {
return;
}

await createMCTransactionDB(txn.toXDR(), jwt);
const response = await createMCTransactionDB(txn.toXDR(), jwt);

addTxnNotification({
title: 'Success',
message: 'Add a signer transaction has been submitted',
type: TxnResponse.Success,
timestamp: Date.now(),
});
if (response?.id != null) {
addTxnNotification({
title: 'Success',
message: 'Change threshold transaction has been submitted',
type: TxnResponse.Success,
timestamp: Date.now(),
});
}
} catch (err) {
handleErrors('Error in changing threshold', err);
useLoadingModal.setAction({
type: 'CLOSE',
});
} finally {
useLoadingModal.setAction({
type: 'CLOSE',
Expand All @@ -126,18 +125,19 @@ const Settings = (props: { accountId: string }) => {
if (!jwt) {
return;
}
await createMCTransactionDB(txn.toXDR(), jwt);
addTxnNotification({
title: 'Success',
message: 'Add a signer transaction has been submitted',
type: TxnResponse.Success,
timestamp: Date.now(),
});

const response = await createMCTransactionDB(txn.toXDR(), jwt);

if (response?.id != null) {
addTxnNotification({
title: 'Success',
message: 'Add a signer transaction has been submitted',
type: TxnResponse.Success,
timestamp: Date.now(),
});
}
} catch (err) {
handleErrors('Error in adding signer', err);
useLoadingModal.setAction({
type: 'CLOSE',
});
} finally {
useLoadingModal.setAction({
type: 'CLOSE',
Expand All @@ -161,13 +161,20 @@ const Settings = (props: { accountId: string }) => {
if (!jwt) {
return;
}
await createMCTransactionDB(txn.toXDR(), jwt);

useLoadingModal.setAction({
type: 'CLOSE',
});
const response = await createMCTransactionDB(txn.toXDR(), jwt);

if (response?.id != null) {
addTxnNotification({
title: 'Success',
message: 'Remove a signer transaction has been submitted',
type: TxnResponse.Success,
timestamp: Date.now(),
});
}
} catch (err) {
handleErrors('Error in removing signer', err);
} finally {
useLoadingModal.setAction({
type: 'CLOSE',
});
Expand Down Expand Up @@ -247,7 +254,7 @@ const Settings = (props: { accountId: string }) => {
})}>
<CreateMultisigForm
onSubmit={(data) => {
handleChangeThreshold(data?.threshold);
handleChangeThreshold(data);
}}>
<CreateMultisigForm.Threshold
minimumSigners={1}
Expand Down
138 changes: 76 additions & 62 deletions src/components/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ const StatusStepMap: Record<MultiSigTransactionStatus, number> = {
[MultiSigTransactionStatus.Executed]: 3,
};

const StatusBadgeMap: Record<MultiSigTransactionStatus, string> = {
[MultiSigTransactionStatus.Executable]: 'Active',
[MultiSigTransactionStatus.Pending]: 'Pending',
[MultiSigTransactionStatus.Executed]: 'Approved',
[MultiSigTransactionStatus.Rejected]: 'Cancelled',
};

const Transactions = ({ address }: ITransactionsProps) => {
const [jwt] = useMCStore((s) => [s.jwt]);
const { getJwtToken } = useMC();
Expand Down Expand Up @@ -68,7 +75,6 @@ const Transactions = ({ address }: ITransactionsProps) => {
offset: Math.max(pagination.offset - 1, 0),
limit: 5,
search: debouncedSearchTerm,
multicliqueAccountAddress: `${address}`,
},
jwt
);
Expand Down Expand Up @@ -121,70 +127,78 @@ const Transactions = ({ address }: ITransactionsProps) => {
{!listTransactions.pending &&
listTransactions.fulfilled &&
!listTransactions.value?.results?.length && <EmptyPlaceholder />}
{listTransactions?.value?.results?.map((item, index) => {
return (
<Accordion.Container
key={index}
id={index}
onClick={() =>
setActiveAccordion(activeAccordion === index ? null : index)
}
color='base'
expanded={index === activeAccordion}>
<Accordion.Header className='flex gap-2 text-sm'>
<div className='grow font-semibold'>{item.callFunc}</div>
<div>
{dayjs(item.createdAt).format('MMMM D, YYYY - h:mm:ss A')}
</div>
<UserTally
value={item.approvals?.length}
over={item.signatories?.length}
/>
<TransactionBadge status='Active' />
</Accordion.Header>
<Accordion.Content className='flex divide-x'>
<div className='w-2/3 px-2 pr-4'>
<div className='flex items-center gap-2'>
<div className='shrink-0 font-semibold'>Call hash:</div>
<div ref={textRef}>
{/* TODO: update hash */}
{truncateMiddle(item.preimageHash, 16, 3)}
{!listTransactions.pending &&
listTransactions?.value?.results?.map((item, index) => {
return (
<Accordion.Container
key={index}
id={index}
onClick={() =>
setActiveAccordion(activeAccordion === index ? null : index)
}
color='base'
expanded={index === activeAccordion}>
<Accordion.Header className='flex gap-2 text-sm'>
<div className='grow font-semibold'>{item.callFunc}</div>
<div>
{dayjs(item.createdAt).format('MMMM D, YYYY - h:mm:ss A')}
</div>
<UserTally
value={item.approvals?.length}
over={item.signatories?.length}
/>
<TransactionBadge
status={StatusBadgeMap[item.status] as any}
/>
</Accordion.Header>
<Accordion.Content className='flex divide-x'>
<div className='w-2/3 px-2 pr-4'>
<div className='flex items-center gap-2'>
<div className='shrink-0 font-semibold'>Call hash:</div>
<div className='hidden' ref={textRef}>
{item.preimageHash}
</div>
<div>
{/* TODO: update hash */}
{truncateMiddle(item.preimageHash, 16, 3)}
</div>
<span
onClick={copyToClipboard}
className='rounded-full p-1 hover:bg-base-200'>
<Copy className='h-4 w-4 cursor-pointer' />
</span>
</div>
<span
onClick={copyToClipboard}
className='rounded-full p-1 hover:bg-base-200'>
<Copy className='h-4 w-4 cursor-pointer' />
</span>
</div>
</div>
<div className='grow space-y-2 px-3'>
<Timeline>
{['Created', 'Approved', 'Executed'].map(
(step, stepIndex) => (
<Timeline.Item
key={`${stepIndex}-${step}`}
{...(stepIndex <= StatusStepMap[item.status] && {
status:
stepIndex === StatusStepMap[item.status]
? 'active'
: 'completed',
})}>
{step}
</Timeline.Item>
)
)}
</Timeline>
<div>Can be executed once threshold is reached</div>
<div className='flex w-full gap-2'>
<button className='btn btn-outline flex-1'>Reject</button>
<button className='btn btn-primary flex-1'>Approve</button>
{/* TODO add execute button */}
<div className='grow space-y-2 px-3'>
<Timeline>
{['Created', 'Approved', 'Executed'].map(
(step, stepIndex) => (
<Timeline.Item
key={`${stepIndex}-${step}`}
{...(stepIndex <= StatusStepMap[item.status] && {
status:
stepIndex === StatusStepMap[item.status]
? 'active'
: 'completed',
})}>
{step}
</Timeline.Item>
)
)}
</Timeline>
<div>Can be executed once threshold is reached</div>
<div className='flex w-full gap-2'>
<button className='btn btn-outline flex-1'>Reject</button>
<button className='btn btn-primary flex-1'>
Approve
</button>
{/* TODO add execute button */}
</div>
</div>
</div>
</Accordion.Content>
</Accordion.Container>
);
})}
</Accordion.Content>
</Accordion.Container>
);
})}
</div>
{!listTransactions.pending &&
Boolean(listTransactions.value?.results?.length) && (
Expand Down
12 changes: 9 additions & 3 deletions src/hooks/useMC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isValidXDR,
numberToU32ScVal,
toBase64,
toScValBytes,
} from '@/utils';
import { signBlob, signTransaction } from '@stellar/freighter-api';
import * as SorobanClient from 'soroban-client';
Expand Down Expand Up @@ -560,7 +561,7 @@ const useMC = () => {
currentWalletAccount?.publicKey,
coreAddress,
'add_signer',
accountToScVal(signerAddress)
toScValBytes(signerAddress)
);
return txn;
};
Expand All @@ -576,7 +577,7 @@ const useMC = () => {
currentWalletAccount?.publicKey,
coreAddress,
'remove_signer',
accountToScVal(signerAddress)
toScValBytes(signerAddress)
);
return txn;
};
Expand Down Expand Up @@ -618,6 +619,11 @@ const useMC = () => {
},
jwtToken
);

if (!mcTxnRes?.preimageHash) {
throw new Error('Error creating a Multiclique Transaction');
}

const signedHash = await signBlob(mcTxnRes.preimageHash, {
accountToSign: currentWalletAccount?.publicKey,
});
Expand All @@ -631,7 +637,7 @@ const useMC = () => {
name: currentWalletAccount.publicKey,
address: currentWalletAccount.publicKey,
},
signature: signedHash,
signature: toBase64(signedHash),
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion src/services/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const patchMultiCliqueTransaction = async (
const body = JSON.stringify(keysToSnakeCase(payload));

const response = await fetch(
`${SERVICE_URL}/multiclique/transactions/${id}`,
`${SERVICE_URL}/multiclique/transactions/${id}/`,
{
method: 'PATCH',
body,
Expand Down
5 changes: 5 additions & 0 deletions src/types/multiCliqueAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ export type MultiCliquePolicy = {
name: string;
active: boolean;
};

export interface Signature {
signature: string;
signatory: Signatory;
}
6 changes: 3 additions & 3 deletions src/types/multisigTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CamelCaseObject } from '@/utils/transformer';
import type { Signatory } from './multiCliqueAccount';
import type { Signatory, Signature } from './multiCliqueAccount';

export enum MultiSigTransactionStatus {
Pending = 'PENDING',
Expand All @@ -14,8 +14,8 @@ export interface RawMultisigTransaction {
preimage_hash: string;
call_func: string;
call_args: Record<string, any>;
approvals: { signature: string; signatory: Signatory }[];
rejections: { signature: string; signatory: Signatory }[];
approvals?: Signature[];
rejections?: Signature[];
status: MultiSigTransactionStatus;
executed_at: string;
created_at: string;
Expand Down
5 changes: 4 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DAO_UNITS, XLM_UNITS } from '@/config';
import { DAO_UNITS,XLM_UNITS } from '@/config';
import BigNumber from 'bignumber.js';
import * as SorobanClient from 'soroban-client';
// @ts-ignore
Expand Down Expand Up @@ -97,6 +97,9 @@ export const toBase64 = (str: string): string => {
export const accountToScVal = (account: string) =>
new SorobanClient.Address(account).toScVal();

export const toScValBytes = (value: string) =>
SorobanClient.xdr.ScVal.scvBytes(new SorobanClient.Address(value).toBuffer());

export const isValidXDR = (xdrString: string, networkParaphrase: string) => {
try {
SorobanClient.TransactionBuilder.fromXDR(xdrString, networkParaphrase);
Expand Down

0 comments on commit e3ece79

Please sign in to comment.