import { ArtifactState, } from '@hyperlane-xyz/provider-sdk/artifact';
import { TokenType, } from '@hyperlane-xyz/provider-sdk/warp';
import { assert, isNullish, isZeroishAddress, ZERO_ADDRESS_HEX_32, } from '@hyperlane-xyz/utils';
import { getAddressEncoder, address as parseAddress, } from '@solana/kit';
import { fetchMintMetadata } from '../accounts/mint.js';
import { concatBytes, u32le, u64le, u8 } from '../codecs/binary.js';
import { DEFAULT_COMPUTE_UNITS, RENT_SYSVAR_ADDRESS, SYSTEM_PROGRAM_ADDRESS, TOKEN_2022_PROGRAM_ADDRESS, } from '../constants.js';
import { resolveProgram } from '../deploy/resolve-program.js';
import { getTokenInitInstruction } from '../instructions/token.js';
import { buildInstruction, readonlyAccount, readonlySigner, writableAccount, writableSigner, } from '../instructions/utils.js';
import { deriveAtaPayerPda, deriveSyntheticMintPda } from '../pda.js';
import { fetchTokenAccount, routerBytesToHex } from './warp-query.js';
import { applyPostInitConfig, assertLocalDecimals, buildBaseInitData, buildFundAtaPayerInstruction, computeWarpTokenUpdateInstructions, remoteDecimalsToScale, scaleToRemoteDecimals, } from './warp-tx.js';
// Borsh discriminator for the Token 2022 InitializeTokenMetadata instruction.
const METADATA_INITIALIZE_DISCRIMINATOR = new Uint8Array([
    210, 225, 30, 162, 88, 184, 77, 141,
]);
const addressEncoder = getAddressEncoder();
/**
 * SPL Token 2022: InitializeMetadataPointer (extension type 39).
 * authority and metadataAddress are encoded into the 66-byte payload.
 */
function createInitializeMetadataPointerInstruction(mint, authority, metadataAddress) {
    const data = new Uint8Array(66);
    data[0] = 39; // extension type: MetadataPointer
    data[1] = 0;
    data.set(addressEncoder.encode(authority), 2);
    data.set(addressEncoder.encode(metadataAddress), 34);
    return buildInstruction(TOKEN_2022_PROGRAM_ADDRESS, [writableAccount(mint)], data);
}
/** SPL Token 2022: InitializeMint2 (discriminator 20). */
function createInitializeMint2Instruction(mint, decimals, mintAuthority) {
    const data = concatBytes(u8(20), // InitializeMint2 discriminator
    u8(decimals), addressEncoder.encode(mintAuthority), u8(0));
    return buildInstruction(TOKEN_2022_PROGRAM_ADDRESS, [writableAccount(mint), readonlyAccount(RENT_SYSVAR_ADDRESS)], data);
}
/** SPL Token 2022: SetAuthority (discriminator 6, authority type 0 = MintTokens). */
function createSetAuthorityInstruction(mint, currentAuthority, newAuthority) {
    const data = concatBytes(u8(6), // SetAuthority discriminator
    u8(0), // authority type: MintTokens
    u8(1), // has new authority
    addressEncoder.encode(newAuthority));
    return buildInstruction(TOKEN_2022_PROGRAM_ADDRESS, [writableAccount(mint), readonlySigner(currentAuthority)], data);
}
/** SPL Token 2022: InitializeTokenMetadata (Borsh discriminator). */
function createInitializeMetadataInstruction(mint, updateAuthority, mintAuthority, name, symbol, uri) {
    const nameBytes = new TextEncoder().encode(name);
    const symbolBytes = new TextEncoder().encode(symbol);
    const uriBytes = new TextEncoder().encode(uri);
    const data = concatBytes(METADATA_INITIALIZE_DISCRIMINATOR, u32le(nameBytes.length), nameBytes, u32le(symbolBytes.length), symbolBytes, u32le(uriBytes.length), uriBytes);
    return buildInstruction(TOKEN_2022_PROGRAM_ADDRESS, [
        writableAccount(mint),
        readonlyAccount(updateAuthority),
        readonlyAccount(mint),
        readonlySigner(mintAuthority),
    ], data);
}
export class SvmSyntheticTokenReader {
    rpc;
    constructor(rpc) {
        this.rpc = rpc;
    }
    async read(programAddress) {
        const programId = parseAddress(programAddress);
        const token = await fetchTokenAccount(this.rpc, programId);
        assert(!isNullish(token), `Synthetic token not initialized at ${programId}`);
        const remoteRouters = {};
        for (const [domain, router] of token.remoteRouters.entries()) {
            remoteRouters[domain] = { address: routerBytesToHex(router) };
        }
        const destinationGas = {};
        for (const [domain, gas] of token.destinationGas.entries()) {
            destinationGas[domain] = gas.toString();
        }
        const { address: mintPda } = await deriveSyntheticMintPda(programId);
        const metadata = await fetchMintMetadata(this.rpc, mintPda);
        const config = {
            type: TokenType.synthetic,
            owner: token.owner ?? ZERO_ADDRESS_HEX_32,
            mailbox: token.mailbox,
            interchainSecurityModule: token.interchainSecurityModule
                ? {
                    artifactState: ArtifactState.UNDERIVED,
                    deployed: { address: token.interchainSecurityModule },
                }
                : undefined,
            hook: token.interchainGasPaymaster
                ? {
                    artifactState: ArtifactState.UNDERIVED,
                    deployed: { address: token.interchainGasPaymaster.programId },
                }
                : undefined,
            remoteRouters,
            destinationGas,
            name: metadata.name,
            symbol: metadata.symbol,
            decimals: token.decimals,
            metadataUri: metadata.uri,
            scale: remoteDecimalsToScale(token.decimals, token.remoteDecimals),
        };
        return {
            artifactState: ArtifactState.DEPLOYED,
            config,
            deployed: { address: programId },
        };
    }
}
export class SvmSyntheticTokenWriter extends SvmSyntheticTokenReader {
    config;
    svmSigner;
    constructor(config, rpc, svmSigner) {
        super(rpc);
        this.config = config;
        this.svmSigner = svmSigner;
    }
    async create(artifact) {
        const receipts = [];
        const tokenConfig = artifact.config;
        assert(tokenConfig.metadataUri !== undefined, 'metadataUri is required for Solana synthetic token deployments');
        assertLocalDecimals(tokenConfig.decimals);
        const remoteDecimals = scaleToRemoteDecimals(tokenConfig.decimals, tokenConfig.scale);
        const { programAddress, receipts: deployReceipts } = await resolveProgram(this.config.program, this.svmSigner, this.rpc, true);
        receipts.push(...deployReceipts);
        const { address: mintPda } = await deriveSyntheticMintPda(programAddress);
        const { address: ataPayerPda } = await deriveAtaPayerPda(programAddress);
        const initData = await buildBaseInitData(tokenConfig, tokenConfig.decimals, remoteDecimals);
        const initIx = await getTokenInitInstruction(programAddress, this.svmSigner.signer, initData, [writableAccount(mintPda), writableAccount(ataPayerPda)]);
        const initMetadataPtrIx = createInitializeMetadataPointerInstruction(mintPda, this.svmSigner.signer.address, mintPda);
        const initMintIx = createInitializeMint2Instruction(mintPda, tokenConfig.decimals, this.svmSigner.signer.address);
        receipts.push(await this.svmSigner.send({
            instructions: [initIx, initMetadataPtrIx, initMintIx],
            computeUnits: DEFAULT_COMPUTE_UNITS,
            skipPreflight: true,
        }));
        // Initialize Token 2022 metadata.
        // Fund mint account to cover the rent increase from metadata TLV being
        // appended to the mint account. Metadata on-chain layout (Borsh):
        //   TLV header(4) + update_authority(32) + mint(32)
        //   + name(4+N) + symbol(4+M) + uri(4+L) + additional_metadata(4)
        const encoder = new TextEncoder();
        const nameLen = encoder.encode(tokenConfig.name).length;
        const symbolLen = encoder.encode(tokenConfig.symbol).length;
        const uriLen = encoder.encode(tokenConfig.metadataUri).length;
        const metadataSize = 4 + 32 + 32 + (4 + nameLen) + (4 + symbolLen) + (4 + uriLen) + 4;
        const metadataRent = await this.rpc
            .getMinimumBalanceForRentExemption(BigInt(metadataSize))
            .send();
        const fundMintIx = buildInstruction(SYSTEM_PROGRAM_ADDRESS, [writableSigner(this.svmSigner.signer), writableAccount(mintPda)], concatBytes(u32le(2), // SystemProgram::Transfer discriminator (u32 LE)
        u64le(metadataRent)));
        const initMetadataIx = createInitializeMetadataInstruction(mintPda, this.svmSigner.signer.address, this.svmSigner.signer, tokenConfig.name, tokenConfig.symbol, tokenConfig.metadataUri);
        // Transfer mint authority to mintPda so the program owns minting.
        // Bundled with metadata init to avoid leaving the deployer as mint
        // authority if a separate authority-transfer tx were to fail.
        const setAuthorityIx = createSetAuthorityInstruction(mintPda, this.svmSigner.signer, mintPda);
        receipts.push(await this.svmSigner.send({
            instructions: [fundMintIx, initMetadataIx, setAuthorityIx],
            skipPreflight: true,
        }));
        const fundAtaPayerIx = await buildFundAtaPayerInstruction(this.rpc, this.svmSigner.signer.address, programAddress, this.config.ataPayerFundingAmount);
        if (fundAtaPayerIx) {
            receipts.push(await this.svmSigner.send({ instructions: [fundAtaPayerIx] }));
        }
        receipts.push(...(await applyPostInitConfig(this.svmSigner, programAddress, tokenConfig)));
        return [
            {
                artifactState: ArtifactState.DEPLOYED,
                config: tokenConfig,
                deployed: { address: programAddress },
            },
            receipts,
        ];
    }
    async update(artifact) {
        const programId = parseAddress(artifact.deployed.address);
        const current = await this.read(programId);
        assert(!isZeroishAddress(current.config.owner), `Cannot update synthetic token ${programId}: token has no owner`);
        return computeWarpTokenUpdateInstructions(current.config, artifact.config, programId, parseAddress(current.config.owner), this.rpc, `synthetic token ${programId}`);
    }
}
//# sourceMappingURL=synthetic-token.js.map