// External dependencies (you'll need to install these)
import CryptoJS from "crypto-js";
import {
Account,
CairoCustomEnum,
CairoOption,
CairoOptionVariant,
CallData,
ec,
hash,
num,
RpcProvider,
stark,
} from "starknet";
// Wallet type configuration
type WalletType = "CHIPI" | "READY";
// Class hashes for each wallet type
const WALLET_CLASS_HASHES = {
CHIPI: "0x53f4f8791ed5bed0fddaa553d180c664e32cfaf8316bb232ae77bb08f459f2a",
READY: "0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f",
};
// RPC endpoints per wallet type
const WALLET_RPC_ENDPOINTS = {
CHIPI: "https://starknet-mainnet.public.blastapi.io/rpc/v0_7",
READY: "https://cloud.argent-api.com/v1/starknet/mainnet/rpc/v0.7",
};
// Deployment data type
interface DeploymentData {
class_hash: string;
salt: string;
unique: string;
calldata: string[];
}
// Encryption utility
const encryptPrivateKey = (
privateKey: string,
password: string,
): string => {
if (!privateKey || !password) {
throw new Error("Private key and password are required");
}
return CryptoJS.AES.encrypt(privateKey, password).toString();
};
// Types (included inline)
interface WalletData {
publicKey: string;
encryptedPrivateKey: string;
}
interface CreateWalletParams {
encryptKey: string;
externalUserId: string;
apiPublicKey: string;
bearerToken: string;
chain: string; // Currently only "STARKNET" is supported
walletType?: WalletType; // Optional, defaults to "CHIPI"
}
// Backend URL constant
const BACKEND_URL = "https://api.chipipay.com/v1";
// Build constructor calldata based on wallet type
const buildConstructorCallData = (
walletType: WalletType,
starkKeyPubAX: string
): string[] => {
if (walletType === "READY") {
// Argent X Account: owner (CairoCustomEnum) + guardian (CairoOption None)
const axSigner = new CairoCustomEnum({
Starknet: { pubkey: starkKeyPubAX },
});
const axGuardian = new CairoOption<unknown>(CairoOptionVariant.None);
return CallData.compile({
owner: axSigner,
guardian: axGuardian,
});
}
// ChipiWallet (default): Simple OpenZeppelin account with just public_key
return CallData.compile({
public_key: starkKeyPubAX,
});
};
// Main function
export const createWallet = async (
params: CreateWalletParams
): Promise<CreateWalletResponse> => {
try {
const {
encryptKey,
apiPublicKey,
bearerToken,
walletType = "CHIPI" // Default to CHIPI wallet
} = params;
// Select RPC endpoint based on wallet type
const rpcUrl = WALLET_RPC_ENDPOINTS[walletType];
const provider = new RpcProvider({ nodeUrl: rpcUrl });
// Generating the private key with Stark Curve
const privateKeyAX = stark.randomAddress();
const starkKeyPubAX = ec.starkCurve.getStarkKey(privateKeyAX);
// Select class hash based on wallet type
const accountClassHash = WALLET_CLASS_HASHES[walletType];
// Build constructor calldata based on wallet type
const constructorCallData = buildConstructorCallData(walletType, starkKeyPubAX);
// Calculate future address of the account
const publicKey = hash.calculateContractAddressFromHash(
starkKeyPubAX,
accountClassHash,
constructorCallData,
0
);
// Initiating Account
const account = new Account(provider, publicKey, privateKeyAX);
// Backend Call API to create the wallet
const typeDataResponse = await fetch(`${BACKEND_URL}/chipi-wallets/prepare-creation`, {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'x-api-key': apiPublicKey,
},
body: JSON.stringify({
publicKey,
walletType,
}),
});
const { typeData, accountClassHash: accountClassHashResponse } = await typeDataResponse.json();
// Sign the message
const userSignature = await account.signMessage(typeData);
const deploymentData: DeploymentData = {
class_hash: accountClassHashResponse,
salt: starkKeyPubAX,
unique: `${num.toHex(0)}`,
calldata: constructorCallData.map((value: any) => num.toHex(value)),
};
const encryptedPrivateKey = encryptPrivateKey(privateKeyAX, encryptKey);
// Call the API to save the wallet in dashboard
const executeTransactionResponse = await fetch(`${BACKEND_URL}/chipi-wallets`, {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'x-api-key': apiPublicKey,
},
body: JSON.stringify({
apiPublicKey,
publicKey,
walletType,
userSignature: {
r: (userSignature as any).r.toString(),
s: (userSignature as any).s.toString(),
recovery: (userSignature as any).recovery
},
typeData,
encryptedPrivateKey,
deploymentData: {
...deploymentData,
salt: `${deploymentData.salt}`,
calldata: deploymentData.calldata.map((data: any) => `${data}`),
}
}),
});
const executeTransaction = await executeTransactionResponse.json();
console.log("Execute transaction: ", executeTransaction);
if (executeTransaction.success) {
return {
success: true,
txHash: executeTransaction.txHash,
wallet: {
publicKey: executeTransaction.walletPublicKey,
encryptedPrivateKey: encryptedPrivateKey,
} as WalletData,
};
} else {
return {
success: false,
txHash: "",
wallet: {
publicKey: "",
encryptedPrivateKey: "",
} as WalletData,
};
}
} catch (error: unknown) {
console.error("Error detallado:", error);
if (error instanceof Error && error.message.includes("SSL")) {
throw new Error(
"SSL connection error. Try using NODE_TLS_REJECT_UNAUTHORIZED=0 or verify the RPC URL"
);
}
throw new Error(
`Error creating wallet: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
};