Skip to content

Commit 1a6dde0

Browse files
committed
add specific exceptions when performing contract transactions
1 parent 1606253 commit 1a6dde0

File tree

8 files changed

+48
-56
lines changed

8 files changed

+48
-56
lines changed

examples/demo-subconnect/.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ module.exports = {
1414
'warn',
1515
{ allowConstantExport: true },
1616
],
17+
"@typescript-eslint/no-explicit-any": "off"
1718
},
1819
}

examples/demo-subconnect/src/components/GreeterBoard.tsx

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,13 @@ import PendingText from '@/components/shared/PendingText.tsx';
55
import { shortenAddress } from '@/utils/string.ts';
66
import { ContractId } from 'contracts/deployments';
77
import { GreeterContractApi } from 'contracts/types/greeter';
8-
import {
9-
useBalance,
10-
useContract,
11-
useContractTx,
12-
useTypink,
13-
useWatchContractEvent,
14-
useWatchContractQuery,
15-
} from 'typink';
8+
import { useContract, useContractTx, useWatchContractEvent, useWatchContractQuery } from 'typink';
169
import { txToaster } from '@/utils/txToaster.tsx';
1710

1811
export default function GreetBoard() {
19-
const { connectedAccount } = useTypink();
2012
const { contract } = useContract<GreeterContractApi>(ContractId.GREETER);
2113
const [message, setMessage] = useState('');
2214
const setMessageTx = useContractTx(contract, 'setMessage');
23-
const balance = useBalance(connectedAccount?.address);
2415

2516
const { data: greet, isLoading } = useWatchContractQuery({
2617
contract,
@@ -30,16 +21,6 @@ export default function GreetBoard() {
3021
const handleUpdateGreeting = async () => {
3122
if (!contract || !message) return;
3223

33-
if (!connectedAccount) {
34-
toast.info('Please connect to your wallet');
35-
return;
36-
}
37-
38-
if (balance?.free === 0n) {
39-
toast.error('Balance insufficient to make transaction.');
40-
return;
41-
}
42-
4324
const toaster = txToaster('Signing transaction...');
4425

4526
try {

examples/demo/.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ module.exports = {
1414
'warn',
1515
{ allowConstantExport: true },
1616
],
17+
"@typescript-eslint/no-explicit-any": "off"
1718
},
1819
}

examples/demo/src/components/GreeterBoard.tsx

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,13 @@ import PendingText from '@/components/shared/PendingText.tsx';
55
import { shortenAddress } from '@/utils/string.ts';
66
import { ContractId } from 'contracts/deployments';
77
import { GreeterContractApi } from 'contracts/types/greeter';
8-
import {
9-
useBalance,
10-
useContract,
11-
useContractTx,
12-
useTypink,
13-
useWatchContractEvent,
14-
useWatchContractQuery,
15-
} from 'typink';
8+
import { useContract, useContractTx, useWatchContractEvent, useWatchContractQuery } from 'typink';
169
import { txToaster } from '@/utils/txToaster.tsx';
1710

1811
export default function GreetBoard() {
19-
const { connectedAccount } = useTypink();
2012
const { contract } = useContract<GreeterContractApi>(ContractId.GREETER);
2113
const [message, setMessage] = useState('');
2214
const setMessageTx = useContractTx(contract, 'setMessage');
23-
const balance = useBalance(connectedAccount?.address);
2415

2516
const { data: greet, isLoading } = useWatchContractQuery({
2617
contract,
@@ -30,16 +21,6 @@ export default function GreetBoard() {
3021
const handleUpdateGreeting = async () => {
3122
if (!contract || !message) return;
3223

33-
if (!connectedAccount) {
34-
toast.info('Please connect to your wallet');
35-
return;
36-
}
37-
38-
if (balance?.free === 0n) {
39-
toast.error('Balance insufficient to make transaction.');
40-
return;
41-
}
42-
4324
const toaster = txToaster('Signing transaction...');
4425

4526
try {

examples/demo/src/components/Psp22Board.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { txToaster } from '@/utils/txToaster.tsx';
1111
export default function Psp22Board() {
1212
const { contract } = useContract<Psp22ContractApi>(ContractId.PSP22);
1313
const { connectedAccount } = useTypink();
14-
const mintTx = useContractTx(contract, 'psp22MintableMint');
14+
const mintTx = useContractTx(contract, 'psp22Transfer');
1515
const inputAddressRef = useRef<HTMLInputElement>(null);
1616
const [address, setAddress] = useState('');
1717
const [watch, setWatch] = useState(false);
@@ -64,7 +64,7 @@ export default function Psp22Board() {
6464
const toaster = txToaster('Signing transaction...');
6565
try {
6666
await mintTx.signAndSend({
67-
args: [BigInt(100 * Math.pow(10, tokenDecimal))],
67+
args: ['5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', BigInt(10000000000000000 * Math.pow(10, tokenDecimal)), '0x'],
6868
callback: ({ status }) => {
6969
console.log(status);
7070

examples/demo/src/utils/txToaster.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ export function txToaster(initialMessage: string = 'Signing transaction...'): Tx
5050
const onError = (e: Error) => {
5151
toast.update(toastId, {
5252
render: (
53-
<p>
54-
Tx Error: <b>{e.message}</b>
55-
</p>
53+
<p>{e.message}</p>
5654
),
5755
type: 'error',
5856
isLoading: false,

packages/typink/src/hooks/useContractTx.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import {
1010
} from 'dedot/contracts';
1111
import { ISubmittableResult } from 'dedot/types';
1212
import { assert, deferred } from 'dedot/utils';
13-
import { TypinkError } from '../utils/index.js';
13+
import { BalanceInsufficientError, ContractMessageError } from '../utils/index.js';
1414
import { useDeepDeps } from './internal/index.js';
15+
import { ISubstrateClient } from 'dedot';
1516

1617
type UseContractTx<A extends GenericContractApi = GenericContractApi> = OmitNever<{
1718
[K in keyof A['tx']]: K extends string ? (K extends `${infer Literal}` ? Literal : never) : never;
@@ -63,8 +64,8 @@ export function useContractTx<
6364
const signAndSend = useMemo(
6465
() => {
6566
return async (o: Parameters<UseContractTxReturnType<T>['signAndSend']>[0]) => {
66-
assert(contract, 'Contract Not Found');
67-
assert(connectedAccount, 'Connected Account Not Found');
67+
assert(contract, 'Contract not found');
68+
assert(connectedAccount, 'No connected account. Please connect your wallet.');
6869

6970
setInProgress(true);
7071
setInBestBlockProgress(true);
@@ -122,18 +123,15 @@ export async function contractTx<
122123
callback?: (result: ISubmittableResult) => void;
123124
} & Args<Pop<Parameters<T['tx'][M]>>>,
124125
): Promise<void> {
125-
// TODO assertions
126-
// TODO check if balance is sufficient
127-
128126
const defer = deferred<void>();
129127

130128
const signAndSend = async () => {
131129
const { contract, fn, args = [], caller, txOptions = {}, callback } = parameters;
132130

131+
await checkBalanceSufficient(contract.client, caller);
132+
133133
try {
134-
// TODO dry running
135134
const dryRunOptions: ContractCallOptions = { caller };
136-
137135
const dryRun = await contract.query[fn](...args, dryRunOptions);
138136
console.log('Dry run result:', dryRun);
139137

@@ -143,8 +141,7 @@ export async function contractTx<
143141
} = dryRun;
144142

145143
if (data && data['isErr'] && data['err']) {
146-
// TODO Add a specific contract level error
147-
throw new TypinkError(JSON.stringify(data['err']));
144+
throw new ContractMessageError(data['err']);
148145
}
149146

150147
const actualTxOptions: ContractTxOptions = {
@@ -179,3 +176,14 @@ export async function contractTx<
179176

180177
return defer.promise;
181178
}
179+
180+
const checkBalanceSufficient = async <T extends GenericContractApi = GenericContractApi>(
181+
client: ISubstrateClient<T['types']['ChainApi']>,
182+
caller: string,
183+
): Promise<void> => {
184+
const balance = await client.query.system.account(caller);
185+
// TODO better calculate reducible balance
186+
if (balance.data.free <= 0n) {
187+
throw new BalanceInsufficientError(caller);
188+
}
189+
};

packages/typink/src/utils/errors.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,26 @@ import { DedotError } from 'dedot/utils';
33
/**
44
* Typink-related errors
55
*/
6-
export class TypinkError extends DedotError {}
6+
export class TypinkError extends DedotError {}
7+
8+
export class ContractMessageError<T extends any> extends TypinkError {
9+
constructor(public error: T, message?: string) {
10+
super(message || `Contract Message Error: ${extractErrorType(error)}`);
11+
}
12+
}
13+
14+
export class BalanceInsufficientError extends TypinkError {
15+
constructor(public caller: string, message?: string) {
16+
super(message || 'Insufficient balance to perform this transaction');
17+
}
18+
}
19+
20+
const extractErrorType = (error: any): string => {
21+
if (typeof error === 'object' && error?.hasOwnProperty('type')) {
22+
return error['type'];
23+
} else if (typeof error === 'string') {
24+
return error;
25+
}
26+
27+
return JSON.stringify(error)
28+
}

0 commit comments

Comments
 (0)