• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cartesi / rollups-explorer / 11943333351

20 Nov 2024 11:09PM UTC coverage: 86.535% (-0.07%) from 86.605%
11943333351

push

github

brunomenezes
chore(ci): Set the Preview endpoint when available in the CI settings.

1276 of 1529 branches covered (83.45%)

Branch coverage included in aggregate %.

9077 of 10435 relevant lines covered (86.99%)

44.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.34
/apps/web/src/components/specification/hooks/useVoucherDecoder.tsx
1
"use client";
1✔
2
import { v2OutputFactoryAbi } from "@cartesi/rollups-wagmi";
1✔
3
import { whatsabi } from "@shazow/whatsabi";
1✔
4
import { any, isNil } from "ramda";
1✔
5
import { isNilOrEmpty, isNotNilOrEmpty } from "ramda-adjunct";
1✔
6
import { useEffect, useState } from "react";
1✔
7
import { Abi, Hex, createPublicClient, decodeFunctionData } from "viem";
1✔
8
import { RollupVersion } from "../../../graphql/explorer/types";
1✔
9
import getSupportedChainInfo, {
10
    SupportedChainId,
11
} from "../../../lib/supportedChains";
1✔
12
import buildTransport from "../../../lib/transport";
1✔
13
import { decodePayload } from "../decoder";
1✔
14
import { Specification } from "../types";
15
import { stringifyContent } from "../utils";
1✔
16

17
type UseVoucherDecoderProps = {
18
    destination: Hex;
19
    payload: Hex;
20
    chainId: number;
21
    /** default to RollupVersion.V1 */
22
    appVersion?: RollupVersion;
23
};
24
const cache = new Map<string, Abi>();
1✔
25

26
const buildClient = (chainId: number) => {
1✔
27
    const chain = getSupportedChainInfo(chainId as SupportedChainId);
5✔
28

29
    if (isNil(chain)) throw new Error(`ChainId ${chainId} is not supported!`);
5!
30

31
    return createPublicClient({
5✔
32
        transport: buildTransport(chain.id),
5✔
33
        chain,
5✔
34
    });
5✔
35
};
5✔
36

37
const fetchAbiFor = async (destination: Hex, chainId: number) => {
1✔
38
    const cacheKey = `${chainId}:${destination}`;
6✔
39
    const abi = cache.get(cacheKey);
6✔
40

41
    if (abi && isNotNilOrEmpty(abi)) return abi;
6✔
42

43
    const result = await whatsabi.autoload(destination, {
5✔
44
        provider: buildClient(chainId),
5✔
45
        followProxies: true,
5✔
46
        onError: () => false,
5✔
47
    });
5✔
48

49
    cache.set(cacheKey, result.abi as Abi);
4✔
50

51
    return result.abi as Abi;
4✔
52
};
4✔
53

54
const buildSpecification = (
1✔
55
    destination: Hex,
5✔
56
    chainId: number,
5✔
57
    abi: Abi,
5✔
58
): Specification => {
5✔
59
    return {
5✔
60
        mode: "json_abi",
5✔
61
        abi,
5✔
62
        name: `Abi for ${destination} on chain ${chainId}`,
5✔
63
        timestamp: Date.now(),
5✔
64
        version: 1,
5✔
65
    };
5✔
66
};
5✔
67

68
type DecodeOutputReturn =
69
    | {
70
          isEtherWithdraw: true;
71
          withdrawData: {
72
              destination: Hex;
73
              value: bigint;
74
              method: "Transfer";
75
              type: "Ether";
76
          };
77
      }
78
    | {
79
          isEtherWithdraw?: false;
80
          payload: Hex;
81
      };
82

83
/**
84
 *
85
 * In case the payload is from a RollupVersion.V1, the payload is returned as is.
86
 *
87
 * @summary Decodes an v2 output and returns the payload as Hex data after decoding or
88
 * prepare an ether-withdraw meta object in a human-readable format as there is no need to fetch the destination's ABI
89
 * to decode. Usually, the data after the decoding with the Output-ABI is `0x` and that is considered an ether-withdraw.
90
 *
91
 * @param payload
92
 * @param appVersion
93
 * @returns
94
 */
95
const decodeOutput = (
1✔
96
    payload: Hex,
8✔
97
    appVersion: RollupVersion,
8✔
98
): DecodeOutputReturn => {
8✔
99
    try {
8✔
100
        if (appVersion === RollupVersion.V2) {
8✔
101
            const { functionName, args } = decodeFunctionData({
4✔
102
                abi: v2OutputFactoryAbi,
4✔
103
                data: payload,
4✔
104
            });
4✔
105

106
            if (functionName === "Voucher") {
4✔
107
                const [destination, value, data] = args;
3✔
108

109
                if (data === "0x") {
3✔
110
                    return {
2✔
111
                        isEtherWithdraw: true,
2✔
112
                        withdrawData: {
2✔
113
                            destination,
2✔
114
                            value,
2✔
115
                            method: "Transfer",
2✔
116
                            type: "Ether",
2✔
117
                        },
2✔
118
                    };
2✔
119
                } else {
3✔
120
                    return {
1✔
121
                        isEtherWithdraw: false,
1✔
122
                        payload: data,
1✔
123
                    };
1✔
124
                }
1✔
125
            }
3✔
126
        }
4✔
127
    } catch (error: any) {
8✔
128
        console.info(error.shortMessage ?? error.message);
1!
129
    }
1✔
130

131
    return { payload };
5✔
132
};
5✔
133

134
interface FetchDestinationABIAndDecodeParams
135
    extends Omit<UseVoucherDecoderProps, "appVersion"> {}
136

137
const fetchDestinationABIAndDecode = async ({
1✔
138
    chainId,
6✔
139
    destination,
6✔
140
    payload,
6✔
141
}: FetchDestinationABIAndDecodeParams) => {
6✔
142
    let result: string;
6✔
143
    const baseMessage = "Skipping voucher decoding. Reason:";
6✔
144

145
    try {
6✔
146
        const abi = await fetchAbiFor(destination, chainId);
6✔
147
        const spec = buildSpecification(destination, chainId, abi);
5✔
148
        const envelope = decodePayload(spec, payload);
5✔
149

150
        if (!envelope.error) result = stringifyContent(envelope.result);
5✔
151
        else {
2✔
152
            console.info(`${baseMessage} ${envelope.error.message}`);
2✔
153
            result = payload;
2✔
154
        }
2✔
155
    } catch (error: any) {
6!
156
        console.info(`${baseMessage} ${error.message}`);
×
157
        result = payload;
×
158
    }
×
159

160
    return result;
5✔
161
};
5✔
162

163
interface Result {
164
    status: "loading" | "idle";
165
    data: string | null;
166
}
167

168
const useVoucherDecoder = ({
1✔
169
    destination,
63✔
170
    payload,
63✔
171
    chainId,
63✔
172
    appVersion = RollupVersion.V1,
63✔
173
}: UseVoucherDecoderProps) => {
63✔
174
    const [result, setResult] = useState<Result>({
63✔
175
        status: "idle",
63✔
176
        data: null,
63✔
177
    });
63✔
178

179
    useEffect(() => {
63✔
180
        if (any(isNilOrEmpty)([destination, chainId, payload, appVersion]))
24✔
181
            return;
24✔
182

183
        setResult((old) => ({ ...old, status: "loading" }));
8✔
184

185
        (async () => {
8✔
186
            const outputResult = decodeOutput(payload, appVersion);
8✔
187
            const result = outputResult.isEtherWithdraw
8✔
188
                ? stringifyContent(outputResult.withdrawData)
2✔
189
                : await fetchDestinationABIAndDecode({
6✔
190
                      chainId,
6✔
191
                      destination,
6✔
192
                      payload: outputResult.payload,
6✔
193
                  });
6✔
194

195
            setResult(() => ({ status: "idle", data: result }));
5✔
196
        })();
8✔
197
    }, [destination, chainId, payload, appVersion]);
63✔
198

199
    return result;
63✔
200
};
63✔
201

202
export default useVoucherDecoder;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc