UNPKG

@keyban/sdk-base

Version:

Keyban Javascript SDK provides core functionalities for the MPC wallet solution, supporting web and Node.js apps with TypeScript, custom storage, and Ethereum blockchain integration.

463 lines (460 loc) 14.8 kB
import { KeybanClientBase, KeybanAccount } from './chunk-XOGOIR6D.js'; import './chunk-5DFR5Z62.js'; import { SdkError } from './chunk-7H2SLR6W.js'; import { Network } from '@keyban/types'; import { http, createPublicClient, hashTypedData, serializeTransaction, keccak256, parseSignature, hashMessage, createWalletClient, isAddress, getContract, erc20Abi, EstimateGasExecutionError, InsufficientFundsError, erc721Abi } from 'viem'; import { toAccount, publicKeyToAddress } from 'viem/accounts'; import * as chains from 'viem/chains'; var ERC1155_ABI_TRANSFER_FROM = [ { type: "function", name: "safeTransferFrom", inputs: [ { name: "from", type: "address", internalType: "address" }, { name: "to", type: "address", internalType: "address" }, { name: "id", type: "uint256", internalType: "uint256" }, { name: "value", type: "uint256", internalType: "uint256" }, { name: "data", type: "bytes", internalType: "bytes" } ], outputs: [], stateMutability: "nonpayable" } ]; var KeybanEvmAccount = class extends KeybanAccount { #publicClient; #walletClient; constructor(api, publicClient, walletClient) { super(api); this.#publicClient = publicClient; this.#walletClient = walletClient; } get address() { return this.#walletClient.account.address.toLowerCase(); } get publicKey() { return this.#walletClient.account.publicKey.toLowerCase(); } /** * Sign an arbitrary message with the EVM account. * @param message - The message to sign. * @returns The signature as a hex string. * @example * const sig = await account.signMessage("hello"); * console.log(sig); */ async signMessage(message) { return this.#walletClient.signMessage({ message }); } /** * Transfer native currency on an EVM chain (e.g., ETH/MATIC) using EIP‑1559. * @param to - Recipient address. * @param value - Amount in wei. * @param [feeDetails] - Optional EIP‑1559 fee overrides; see {@link EvmFeeDetails}. * @returns Transaction hash. * @throws {SdkError} {@link SdkError.types.AddressInvalid} | {@link SdkError.types.AmountInvalid} | {@link SdkError.types.InsufficientFunds} * @example * const tx = await account.transfer("0x0000000000000000000000000000000000000001", 1_000_000_000_000_000n); * console.log(tx); */ async transfer(to, value, feeDetails) { if (!isAddress(to)) { throw new SdkError( SdkError.types.AddressInvalid, "KeybanAccount.transfer" ); } if (value <= 0n) { throw new SdkError( SdkError.types.AmountInvalid, "KeybanAccount.transfer" ); } return this.#walletClient.sendTransaction({ to, value, type: "eip1559", ...feeDetails }).catch((err) => { throw err.cause; }); } /** * Estimate EIP‑1559 fees for a native transfer. * @param to - Recipient address. * @returns An estimation with {@link EvmFeeDetails} under `details`. */ async estimateTransfer(to) { const [{ maxFeePerGas, maxPriorityFeePerGas }, gasCost] = await Promise.all( [ this.#publicClient.estimateFeesPerGas({ type: "eip1559" }), this.#publicClient.estimateGas({ to, account: this.address }) ] ); return { maxFees: maxFeePerGas * gasCost, details: { maxFeePerGas, maxPriorityFeePerGas } }; } /** * Transfer ERC‑20 tokens. * @param params - Transfer parameters including `contractAddress`, `to`, `value`, and optional `fees`. * @returns Transaction hash. * @throws {SdkError} {@link SdkError.types.AddressInvalid} | {@link SdkError.types.AmountInvalid} | {@link SdkError.types.RecipientAddressEqualsSender} | {@link SdkError.types.InsufficientFunds} * @example * const tx = await account.transferERC20({ contractAddress, to, value: 1_000_000n }); * console.log(tx); */ async transferERC20(params) { const { contractAddress, to, value, fees } = params; if (!isAddress(to)) { throw new SdkError( SdkError.types.AddressInvalid, "KeybanAccount.transferERC20" ); } if (!isAddress(contractAddress)) { throw new SdkError( SdkError.types.AddressInvalid, "KeybanAccount.transferERC20" ); } if (to === this.address) { throw new SdkError( SdkError.types.RecipientAddressEqualsSender, "KeybanAccount.transferERC20" ); } if (value <= 0n) { throw new SdkError( SdkError.types.AmountInvalid, "KeybanAccount.transferERC20" ); } const erc20Contract = getContract({ address: contractAddress, abi: erc20Abi, client: { public: this.#publicClient, wallet: this.#walletClient } }); return erc20Contract.write.transfer([to, value], fees).catch((err) => { switch (true) { case err.cause.cause instanceof InsufficientFundsError: case err.cause.cause instanceof EstimateGasExecutionError: throw new SdkError( SdkError.types.InsufficientFunds, "KeybanAccount.transferERC20" ); default: throw err.cause; } }); } /** * Estimate EIP‑1559 fees for an ERC‑20 `transfer`. * @param params - Estimation parameters without explicit fees. * @returns An estimation with {@link EvmFeeDetails} under `details`. */ async estimateERC20Transfer(params) { const { contractAddress, to, value } = params; const [{ maxFeePerGas, maxPriorityFeePerGas }, gasCost] = await Promise.all( [ this.#publicClient.estimateFeesPerGas({ type: "eip1559" }), this.#publicClient.estimateContractGas({ address: contractAddress, abi: erc20Abi, functionName: "transfer", args: [to, value], account: this.address }) ] ); return { maxFees: maxFeePerGas * gasCost, details: { maxFeePerGas, maxPriorityFeePerGas } }; } /** * Transfer an NFT (ERC‑721 / ERC‑1155). * @param params - NFT transfer parameters. For ERC‑1155, `value` must be provided and > 0. For ERC‑721, `value` is ignored or 1. * @returns Transaction hash. * @throws {SdkError} {@link SdkError.types.AddressInvalid} | {@link SdkError.types.RecipientAddressEqualsSender} | {@link SdkError.types.AmountRequired} | {@link SdkError.types.AmountInvalid} | {@link SdkError.types.AmountIrrelevant} | {@link SdkError.types.InvalidNftStandard} | {@link SdkError.types.InsufficientFunds} * @example * await account.transferNft({ contractAddress, tokenId: 1n, to, standard: "ERC721" }); */ async transferNft(params) { const { contractAddress, tokenId, to, value, standard, fees } = params; if (!isAddress(to)) { throw new SdkError( SdkError.types.AddressInvalid, "KeybanAccount.transferNft" ); } if (!isAddress(contractAddress)) { throw new SdkError( SdkError.types.AddressInvalid, "KeybanAccount.transferNft" ); } if (to === this.address) { throw new SdkError( SdkError.types.RecipientAddressEqualsSender, "KeybanAccount.transferNft" ); } if (standard === "ERC1155") { if (value === void 0) { throw new SdkError( SdkError.types.AmountRequired, "KeybanAccount.transferNft" ); } if (value <= 0n) { throw new SdkError( SdkError.types.AmountInvalid, "KeybanAccount.transferNft" ); } return this.#transferERC1155({ contractAddress, tokenId, value, to, fees }); } if (standard === "ERC721") { if (value !== void 0 && value !== 1n) { throw new SdkError( SdkError.types.AmountIrrelevant, "KeybanAccount.transferNft" ); } return this.#transferERC721({ contractAddress, tokenId, to, fees }); } throw new SdkError( SdkError.types.InvalidNftStandard, "KeybanAccount.transferNft" ); } async #transferERC721({ contractAddress, tokenId, to, fees }) { const erc721Contract = getContract({ address: contractAddress, abi: erc721Abi, client: { public: this.#publicClient, wallet: this.#walletClient } }); const from = this.address; return erc721Contract.write.transferFrom([from, to, tokenId], fees).catch((err) => { switch (true) { case err.cause.cause instanceof InsufficientFundsError: case err.cause.cause instanceof EstimateGasExecutionError: throw new SdkError( SdkError.types.InsufficientFunds, "KeybanAccount.transferNft" ); default: throw err.cause; } }); } async #transferERC1155({ contractAddress, tokenId, value, to, fees }) { const erc1155Contract = getContract({ address: contractAddress, abi: ERC1155_ABI_TRANSFER_FROM, client: { public: this.#publicClient, wallet: this.#walletClient } }); const from = this.address; return erc1155Contract.write.safeTransferFrom([from, to, tokenId, value, ""], fees).catch((err) => { switch (true) { case err.cause.cause instanceof InsufficientFundsError: case err.cause.cause instanceof EstimateGasExecutionError: throw new SdkError( SdkError.types.InsufficientFunds, "KeybanAccount.transferNft" ); default: throw err.cause; } }); } /** * Estimate EIP‑1559 fees for an NFT transfer. * @param params - Estimation parameters. * @returns An estimation with {@link EvmFeeDetails} under `details`. */ async estimateNftTransfer(params) { const { standard, contractAddress, tokenId, to, value } = params; if (standard === "ERC1155") { return this.#estimateERC1155Transfer({ contractAddress, tokenId, to, value }); } if (standard === "ERC721") { if (value !== void 0 && value !== 1n) { throw new SdkError( SdkError.types.AmountIrrelevant, "KeybanAccount.transferNft" ); } return this.#estimateERC721Transfer({ contractAddress, tokenId, to }); } throw new SdkError( SdkError.types.InvalidNftStandard, "KeybanAccount.estimateNftTransfer" ); } async #estimateERC721Transfer({ contractAddress, tokenId, to }) { const from = this.address; const [{ maxFeePerGas, maxPriorityFeePerGas }, gasCost] = await Promise.all( [ this.#publicClient.estimateFeesPerGas({ type: "eip1559" }), this.#publicClient.estimateContractGas({ address: contractAddress, abi: erc721Abi, functionName: "transferFrom", args: [from, to, tokenId], account: this.address }) ] ); return { maxFees: maxFeePerGas * gasCost, details: { maxFeePerGas, maxPriorityFeePerGas } }; } async #estimateERC1155Transfer({ contractAddress, tokenId, to, value }) { const from = this.address; const [{ maxFeePerGas, maxPriorityFeePerGas }, gasCost] = await Promise.all( [ this.#publicClient.estimateFeesPerGas({ type: "eip1559" }), this.#publicClient.estimateContractGas({ address: contractAddress, abi: ERC1155_ABI_TRANSFER_FROM, functionName: "safeTransferFrom", args: [from, to, tokenId, value, ""], account: this.address }) ] ); return { maxFees: maxFeePerGas * gasCost, details: { maxFeePerGas, maxPriorityFeePerGas } }; } }; // src/evm/client.ts var KeybanEvmClient = class extends KeybanClientBase { #viem; constructor(config, metadataConfig) { super(config, metadataConfig); const transport = this.metadataConfig.then( (config2) => http(config2.network.rpcUrl) ); const chain = { [Network.EthereumAnvil]: chains.anvil, [Network.PolygonAmoy]: chains.polygonAmoy, [Network.StarknetDevnet]: null, [Network.StarknetSepolia]: null, [Network.StarknetMainnet]: null, [Network.StellarQuickstart]: null, [Network.StellarTestnet]: null, [Network.StellarMainnet]: null }[this.network]; const publicClient = transport.then( (transport2) => createPublicClient({ chain, transport: transport2 }) ); this.#viem = { chain, transport, publicClient }; } /** * Initialize the EVM account for the configured network. * Retrieves or creates a client share (via DKG), derives the public key/address, * and returns a {@link KeybanEvmAccount} bound to viem clients. * @returns A ready-to-use {@link KeybanEvmAccount}. * @example * const account = await evmClient.initialize(); * console.log(account.publicKey, account.address); */ async initialize() { const key = `ecdsa:${this.network}`; let clientShare = await this.clientShareProvider.get(key); if (!clientShare) { clientShare = await this.api.ecdsa.dkg(); await this.clientShareProvider.set(key, clientShare); } const publicKey = await this.api.ecdsa.publicKey(clientShare); const account = toAccount({ address: publicKeyToAddress(publicKey), signMessage: async ({ message }) => { const hash = hashMessage(message, "hex"); return this.api.ecdsa.sign(clientShare, hash); }, signTransaction: async (transaction, options) => { const serializer = options?.serializer ?? serializeTransaction; const signableTransaction = transaction.type === "eip4844" ? { ...transaction, sidecars: false } : transaction; const signature = await this.api.ecdsa.sign(clientShare, keccak256(serializer(signableTransaction))).then(parseSignature); return serializer(transaction, signature); }, signTypedData: async (typedDataDefinition) => { const hash = hashTypedData(typedDataDefinition); const data = await this.api.ecdsa.sign(clientShare, hash); return data; } }); account.publicKey = publicKey; const publicClient = await this.#viem.publicClient; const walletClient = createWalletClient({ chain: publicClient.chain, transport: await this.#viem.transport, account }); return new KeybanEvmAccount(this.api, publicClient, walletClient); } }; export { KeybanEvmClient }; //# sourceMappingURL=evm-OGWCUTTG.js.map //# sourceMappingURL=evm-OGWCUTTG.js.map