@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
JavaScript
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