UNPKG

@axiom-crypto/keystore-sdk

Version:

Keystore Rollup SDK

251 lines (181 loc) 9.37 kB
# Keystore SDK ## Introduction Keystore Typescript SDK to interact with Axiom keystore rollup. ## Installation ```sh npm install @axiom-crypto/keystore-sdk ``` ## Usage ### Selecting a Signature Prover Client You can create your own authentication rule and signature prover infrastructure, or you can use one that's already built. The Keystore SDK can accept a custom authentication rule component that conforms to the `CustomSignatureProver` interface. To use a specific signature prover client (m-of-n ECDSA in this example), you can use: ```typescript import { MOfNEcdsaSignatureProver, M_OF_N_ECDSA_SIG_PROVER_URL } from "@axiom-crypto/keystore-sdk"; const mOfNEcdsaClient = createSignatureProverClient({ url: M_OF_N_ECDSA_SIG_PROVER_URL, ...MOfNEcdsaSignatureProver, }); ``` #### Creating a Custom Signature Prover Client You can create a custom signature prover client by extending `CustomSignatureProver` with 3 generic types that correspond to the fields in your custom authentication rule for `keyData`, `authData`, and `AuthInputs`. You can see the [m-of-n ECDSA Signature Prover Keystore SDK component here](./src/signature-provers/ecdsa.ts). ### Keystore Account To initialize a new keystore account, you'll need to pass in the keccak256 hashed keyData (`dataHash`) and verifying key (`vkey`) from your desired Signature Prover. In both cases, you can also pass in an optional `NodeClient` for additional functionality. ```typescript // Create a new NodeClient for querying the keystore rollup const nodeClient = createNodeClient({ url: NODE_URL }); // Counterfactual account initialization const acct = initAccountCounterfactual({ salt, dataHash, vkey, nodeClient }); ``` Alterntaively, you can also initialize a keystore account with a known keystore address (32 bytes). You'll pass in the address instead of salt, along with the dataHash and vkey as before. ```typescript // Initializing an account with a known address (you still need to pass in the `dataHash` and `vkey`) const acct = initAccountFromAddress({ address, dataHash, vkey, nodeClient }); ``` You can calculate the `dataHash` with the following signature prover client method: ```typescript // Encoding the `keyData` for m-of-n ECDSA signature prover and then hashing it to get the `dataHash` const keyData = mOfNEcdsaClient.keyDataEncoder({ codehash: EXAMPLE_USER_CODEHASH, m: BigInt(1), signersList: [account.address], }); const dataHash = keccak256(keyData); ``` You can use `M_OF_N_ECDSA_VKEY` as the `vkey` and `SAMPLE_USER_CODEHASH` as the `codeHash`. ### Transactions The SDK supports all transaction types of the keystore rollup, including `Deposit`, `Withdraw`, and `Update`. #### Deposit To create a client for the `Deposit` transaction type, you can use the `createDepositTransactionClient` function. You'll need to provide the recipient `keystoreAddress` and the deposit `amt`. ```typescript const depositTx = await createDepositTransactionClient({ keystoreAddress: userAcct.address, amt: parseEther("0.01"), }); ``` #### Withdraw To create a client for the `Withdraw` transaction type, you can use the `createWithdrawTransactionClient` function. You'll need to provide the withdrawal `amt`, the recipient address `to` on L1, the `userAcct` (the keystore account initiating the withdrawal). ```typescript const withdrawTx = await createWithdrawTransactionClient({ amt: parseEther("0.005"), to: account.address, userAcct, }); ``` #### Update To create a client for the `Update` transaction type, you can use the `createUpdateTransactionClient` function. In addition to the user keystore account we created earlier, we'll be providing an optional Sponsor Account that will be sponsoring the `Update` transaction on the keystore rollup. ```typescript const sponsorAcct = initAccountFromAddress({ address: AXIOM_SPONSOR_KEYSTORE_ADDR, dataHash: AXIOM_SPONSOR_DATA_HASH, vkey: mOfNEcdsaClient.vkey, nodeClient, }); const updateTx = await createUpdateTransactionClient({ newUserData: keyData, newUserVkey: mOfNEcdsaClient.vkey, userAcct, sponsorAcct, }); ``` ### Authenticating an L2 Transaction First we obtain the transaction's signature using our account's private key: ```typescript const txSignature = await updateTx.sign(account.privateKey); ``` Once we've signed the transaction, we'll need to use the signature prover client to generate both the user and sponsor `AuthInputs` structs that can be passed into the signature prover client's `authenticateSponsoredTransaction` function. ```typescript // Make user and sponsor auth inputs for the m-of-n ECDSA signature prover const userAuthInputs = mOfNEcdsaClient.makeAuthInputs({ codehash: EXAMPLE_USER_CODEHASH, signatures: [txSignature], signersList: [account.address], }); const sponsorAuthInputs = mOfNEcdsaClient.makeAuthInputs({ codehash: AXIOM_SPONSOR_CODEHASH, signatures: [], signersList: [AXIOM_SPONSOR_EOA], }); // Send authentication data to the signature prover const authHash = await mOfNEcdsaClient.authenticateSponsoredTransaction({ transaction: updateTx.toBytes(), sponsoredAuthInputs: { userAuthInputs, sponsorAuthInputs, }, }); // Wait for authentication (may take several minutes) const authenticatedTx = await mOfNEcdsaClient.waitForSponsoredAuthentication({ hash: authHash }); ``` ### Send an L2 Transaction You can send an authenticated transaction to the sequencer by creating a SequencerClient and using the `sendRawTransaction` function with the authenticated transaction from the previous section. You can then call the `waitForTransactionReceipt` function to fulfill when the transaction receipt is ready. ```typescript // Create a SequencerClient const sequencerClient = createSequencerClient({ url: SEQUENCER_URL }); // Send the transaction to the sequencer const txHash = await sequencerClient.sendRawTransaction({ data: authenticatedTx }); // Wait for the transaction receipt const receipt = await sequencerClient.waitForTransactionReceipt({ hash: txHash }); ``` ### Send an L1-Initiated Tranaction To perform actions such as depositing funds into your keystore account on the L2 rollup, you need to initiate a transaction from L1. This involves using an L1 `WalletClient` extended with specific L1 bridge interaction actions provided by the SDK. ```typescript import { publicActionsL1, walletActionsL1 } from "@axiom-crypto/keystore-sdk"; import { createWalletClient, publicActions } from "viem"; const l1Client = createWalletClient({ account, transport: http(config.l1RpcUrl), }) .extend(publicActions) .extend(publicActionsL1()) .extend(walletActionsL1()); ``` Next, prepare the L1 transaction data using a specific transaction client (e.g., `createDepositTransactionClient`). Then, send this transaction to the L1 bridge contract using the `initiateL1Transaction` method on your extended L1 client. Once the L1 transaction is confirmed, retrieve its receipt. From this L1 receipt, you can extract the corresponding L2 transaction hash using `getL2TransactionHashes`. Finally, use a `SequencerClient` (or `NodeClient`) to wait for the L2 transaction to be processed and get its receipt. ```typescript // Send the deposit transaction to L1 const l1TxHash = await l1Client.initiateL1Transaction({ bridgeAddress: config.bridgeAddress, txClient: depositTx, }); console.log("L1 transaction hash:", l1TxHash); // Fetch deposit transaction hash const l1TxReceipt = await sequencerClient.waitForTransactionReceipt({ hash: l1TxHash }); const [l2TxHash] = getL2TransactionHashes(l1TxReceipt); // Fetch deposit transaction receipt const l2TxReceipt = await l2Client.waitForTransactionReceipt({ hash: l2TxHash }); ``` ### Finalize Withdrawal To finalize a withdrawal on the keystore rollup—meaning withdrawing funds from the keystore rollup (L2) to Ethereum (L1), you first need to send a withdrawal transaction on L2 and wait for it to be finalized. After that, you build the finalization arguments and call the `finalizeWithdrawal` method on the L1 client. To finalize a withdrawal transaction: ```typescript await l2Client.waitForTransactionFinalization({ hash: withdrawTxHash }); const finalizationArgs = await l2Client.buildFinalizeWithdrawalArgs({ transactionHash: withdrawTxHash, }); const l1TxHash = await l1Client.finalizeWithdrawal({ bridgeAddress, ...finalizationArgs, }); ``` ### Query the Chain You can query the keystore rollup chain using the `KeystoreNodeProvider`. This provider enables you to retrieve various pieces of on-chain data, such as transaction details, receipts, blocks, and rollup state: ```typescript const nodeClient = createNodeClient({ url: NODE_URL }); // get transaction by hash const tx = await nodeClient.getTransactionByHash({ hash }); // get transaction receipt by hash const receipt = await nodeClient.getTransactionReceipt({ hash }); // get the latest block with full transactions const block = await nodeClient.getBlockByNumber({ block: BlockTag.Latest, txKind: BlockTransactionsKind.Full, }); // get account state const accountState = await nodeClient.getStateAt({ address: keystoreAddress, block: BlockTag.Latest, }); ``` ## Examples For a complete demonstration, take a look at our [node.js script examples](./examples/) or [React example](https://github.com/axiom-crypto/example-keystore-web).