import * as Address from 'ox/Address';
import * as Hash from 'ox/Hash';
import * as Hex from 'ox/Hex';
import * as Provider from 'ox/Provider';
import * as RpcRequest from 'ox/RpcRequest';
import { getTransactionReceipt } from '../actions/public/getTransactionReceipt.js';
import { sendTransaction } from '../actions/wallet/sendTransaction.js';
import { sendTransactionSync } from '../actions/wallet/sendTransactionSync.js';
import { createClient } from '../clients/createClient.js';
import { createTransport, } from '../clients/transports/createTransport.js';
import * as Transaction from './Transaction.js';
/**
 * Creates a relay transport that routes requests between
 * the default transport or the relay transport.
 *
 * All `eth_fillTransaction` requests are sent to the relay with the request's
 * `feePayer` value preserved so the relay can decide whether to sponsor the transaction.
 *
 * The policy parameter controls how the relay handles sponsored transactions:
 * - `'sign-only'`: Relay co-signs the transaction and returns it to the client transport, which then broadcasts it via the default transport
 * - `'sign-and-broadcast'`: Relay co-signs and broadcasts the transaction directly
 *
 * @param defaultTransport - The default transport to use.
 * @param relayTransport - The relay transport to use.
 * @param parameters - Configuration parameters.
 * @returns A relay transport.
 */
export function withRelay(defaultTransport, relayTransport, parameters) {
    const { policy = 'sign-only' } = parameters ?? {};
    return (config) => {
        const transport_default = defaultTransport(config);
        const transport_relay = relayTransport(config);
        return createTransport({
            key: withRelay.type,
            name: 'Relay Proxy',
            async request({ method, params }, options) {
                if (method === 'eth_fillTransaction')
                    return transport_relay.request({ method, params }, options);
                if (method === 'eth_sendRawTransactionSync' ||
                    method === 'eth_sendRawTransaction') {
                    const serialized = params[0];
                    const transaction = Transaction.deserialize(serialized);
                    // Serialized Tempo envelopes encode `feePayer: true` as a missing fee payer
                    // signature until the relay co-signs the transaction.
                    if (transaction.feePayerSignature === null) {
                        // For 'sign-and-broadcast', relay signs and broadcasts
                        if (policy === 'sign-and-broadcast')
                            return transport_relay.request({ method, params }, options);
                        // For 'sign-only', request signature from relay using eth_signRawTransaction
                        {
                            // Request signature from relay using eth_signRawTransaction
                            const signedTransaction = await transport_relay.request({
                                method: 'eth_signRawTransaction',
                                params: [serialized],
                            }, options);
                            // Broadcast the signed transaction via the default transport
                            return transport_default.request({ method, params: [signedTransaction] }, options);
                        }
                    }
                }
                return (await transport_default.request({ method, params }, options));
            },
            type: withRelay.type,
        });
    };
}
/** @deprecated Use `withRelay` instead. */
export function withFeePayer(defaultTransport, relayTransport, parameters) {
    const { policy = 'sign-only' } = parameters ?? {};
    return (config) => {
        const transport_default = defaultTransport(config);
        const transport_relay = relayTransport(config);
        return createTransport({
            key: withFeePayer.type,
            name: 'Relay Proxy',
            async request({ method, params }, options) {
                if (method === 'eth_fillTransaction') {
                    const request = params?.[0];
                    if (request &&
                        typeof request === 'object' &&
                        'feePayer' in request &&
                        request.feePayer === true)
                        return transport_relay.request({ method, params }, options);
                }
                if (method === 'eth_sendRawTransactionSync' ||
                    method === 'eth_sendRawTransaction') {
                    const serialized = params[0];
                    const transaction = Transaction.deserialize(serialized);
                    // Serialized Tempo envelopes encode `feePayer: true` as a missing fee payer
                    // signature until the relay co-signs the transaction.
                    if (transaction.feePayerSignature === null) {
                        // For 'sign-and-broadcast', relay signs and broadcasts
                        if (policy === 'sign-and-broadcast')
                            return transport_relay.request({ method, params }, options);
                        // For 'sign-only', request signature from relay using eth_signRawTransaction
                        {
                            // Request signature from relay using eth_signRawTransaction
                            const signedTransaction = await transport_relay.request({
                                method: 'eth_signRawTransaction',
                                params: [serialized],
                            }, options);
                            // Broadcast the signed transaction via the default transport
                            return transport_default.request({ method, params: [signedTransaction] }, options);
                        }
                    }
                }
                return (await transport_default.request({ method, params }, options));
            },
            type: withFeePayer.type,
        });
    };
}
/**
 * Creates a transport that instruments a compatibility layer for
 * `wallet_` RPC actions (`sendCalls`, `getCallsStatus`, etc).
 *
 * @param transport - Transport to wrap.
 * @returns Transport.
 */
export function walletNamespaceCompat(transport, options) {
    const { account } = options;
    const sendCallsMagic = Hash.keccak256(Hex.fromString('TEMPO_5792'));
    return (options) => {
        const t = transport(options);
        const chain = options.chain;
        return {
            ...t,
            async request(args) {
                const request = RpcRequest.from(args);
                const client = createClient({
                    chain,
                    transport,
                });
                if (request.method === 'wallet_sendCalls') {
                    const params = request.params[0] ?? {};
                    const { capabilities, chainId, from } = params;
                    const { sync, ...properties } = capabilities ?? {};
                    if (!chainId)
                        throw new Provider.UnsupportedChainIdError();
                    if (Number(chainId) !== client.chain.id)
                        throw new Provider.UnsupportedChainIdError();
                    if (from && !Address.isEqual(from, account.address))
                        throw new Provider.DisconnectedError();
                    const calls = (params.calls ?? []).map((call) => ({
                        to: call.to,
                        value: call.value ? BigInt(call.value) : undefined,
                        data: call.data,
                    }));
                    const hash = await (async () => {
                        if (!sync)
                            return sendTransaction(client, {
                                account,
                                ...(properties ? properties : {}),
                                calls,
                            });
                        const { transactionHash } = await sendTransactionSync(client, {
                            account,
                            ...(properties ? properties : {}),
                            calls,
                        });
                        return transactionHash;
                    })();
                    const id = Hex.concat(hash, Hex.padLeft(chainId, 32), sendCallsMagic);
                    return {
                        capabilities: { sync },
                        id,
                    };
                }
                if (request.method === 'wallet_getCallsStatus') {
                    const [id] = request.params ?? [];
                    if (!id)
                        throw new Error('`id` not found');
                    if (!id.endsWith(sendCallsMagic.slice(2)))
                        throw new Error('`id` not supported');
                    Hex.assert(id);
                    const hash = Hex.slice(id, 0, 32);
                    const chainId = Hex.slice(id, 32, 64);
                    const receipt = await getTransactionReceipt(client, { hash });
                    return {
                        atomic: true,
                        chainId: Number(chainId),
                        id,
                        receipts: [receipt],
                        status: receipt.status === 'success' ? 200 : 500,
                        version: '2.0.0',
                    };
                }
                return t.request(args);
            },
        };
    };
}
//# sourceMappingURL=Transport.js.map