Skip to content

Refactor/replace ethers with viem#15

Open
Sednaoui wants to merge 7 commits intomainfrom
refactor/replace-ethers-with-viem
Open

Refactor/replace ethers with viem#15
Sednaoui wants to merge 7 commits intomainfrom
refactor/replace-ethers-with-viem

Conversation

@Sednaoui
Copy link
Member

@Sednaoui Sednaoui commented Mar 7, 2026

Summary by CodeRabbit

  • Refactor

    • Replaced underlying crypto/key handling and account generation across authentication, passkeys, multisig, recovery, and simulation flows for more consistent and robust behavior.
    • Rewrote passkey/WebAuthn internals to use native crypto and Buffer-based encodings for improved compatibility and reliability.
  • Chores

    • Bumped JavaScript compilation target to ES2022.

Sednaoui and others added 6 commits March 7, 2026 13:02
Fixes build errors from the ox package (transitive dep of viem) which
ships raw .ts files requiring es2021+ features (replaceAll, override).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use generatePrivateKey + privateKeyToAccount from viem/accounts
instead of ethers Wallet.createRandom() in multisig, recovery,
simulate-with-tenderly, eip-7702 sponsored-gas, and nested-safe-accounts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sion

Use createPublicClient + client.readContract instead of ethers Contract
and JsonRpcProvider for the ERC-20 balanceOf check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use viem sign() + parseSignature for delegation authorization and
UserOperation signing. Replace JsonRpcProvider with createPublicClient.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ethers utility functions with viem equivalents (keccak256, sha256,
toHex, hexToBytes, toBytes). Replace @noble/curves/p256 with node:crypto
for P-256 key generation and signing.

Simplify binary protocol construction by using Buffer.concat with an
explicit buildAuthenticatorData helper instead of abusing encodePacked
for binary data. Use native base64url encoding.

Fix Buffer pool bug in extractPublicKey where DataView was constructed
without byteOffset, reading garbage from the shared 8KB ArrayBuffer pool.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use viem hexToBytes, keccak256, toBytes, numberToBytes instead of ethers
getBytes, id, toBeArray in passkeys/index.ts, passkeys/passkeys-v0.2.1.ts,
and chain-abstraction/add-owner-passkey.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

📝 Walkthrough

Walkthrough

This PR migrates cryptographic, account, and on-chain utilities from ethers.js to viem and Node crypto: replacing Wallet-based key generation with viem's generatePrivateKey/privateKeyToAccount, swapping ethers encoding/hash helpers for viem equivalents, refactoring WebAuthn to Buffer/crypto-based flows, and updating the TS target to ES2022.

Changes

Cohort / File(s) Summary
Account & Key Management
chain-abstraction/add-owner-passkey.ts, eip-7702/upgrade-eoa-to-7702-smart-account-sponsored-gas.ts, multisig/multisig.ts, nested-safe-accounts/nested-safe-accounts.ts, recovery/recovery.ts, simulate-with-tenderly/simulate-with-tenderly.ts
Replaced ethers Wallet.createRandom() usage with viem generatePrivateKey() + privateKeyToAccount(). Updated address/privateKey access to use viem account objects and adjusted related variable assignments.
EIP-7702 Wallet Signing
eip-7702/upgrade-eoa-to-7702-smart-account-wallet-signed.ts
Replaced ethers signing and nonce calls with viem client/sign helpers and client.getTransactionCount; switched hex encoding helpers (toHex) and adapted signature r/s/v (yParity) handling to new signature shapes.
Passkeys Encoding
passkeys/index.ts, passkeys/passkeys-v0.2.1.ts, chain-abstraction/add-owner-passkey.ts
Swapped ethers encoding/hash utilities (ethers.id, getBytes, toBeArray) for viem helpers (keccak256, hexToBytes, toBytes, numberToBytes). Adjusted user id, challenge, and WebAuthn challenge encodings.
WebAuthn & Credential Implementation
passkeys/webauthn.ts
Major refactor: replaced noble/ethers crypto with Node.js crypto + Buffer-based handling; Credential now stores a KeyObject and uncompressed public key bytes, id is Hex from keccak256(pubkey); added buildAuthenticatorData, updated attestation/assertion flows, COSE and base64url handling use Buffer/Hex. Public APIs/types adjusted accordingly.
On-chain Reads & ABI
spend-permission/spend-permission.ts
Replaced ethers provider/Contract balance reads with viem PublicClient.readContract and parseAbi. Adjusted call patterns and client usage for ERC20 queries.
Tooling / Config
tsconfig.json
Raised TypeScript target from es2020 to es2022.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble bytes, then do a flip,

From Wallet burrows to viem's hip trip,
Keys now grow where carrots once lay,
Buffers hum and hashes play,
A hopping cheer for the new dev day!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main objective of the pull request: replacing ethers library with viem across multiple files throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
eip-7702/upgrade-eoa-to-7702-smart-account-wallet-signed.ts (1)

88-95: ⚠️ Potential issue | 🟠 Major

Use smartAccount.signUserOperation() for the UserOperation signature.

The UserOperation signature at line 94 should use smartAccount.signUserOperation(userOperation, eoaDelegatorPrivateKey, chainId) instead of the raw viem sign() method. This is required by the coding guidelines and ensures consistency with the corresponding non-wallet-signed 7702 example and other examples in the repository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eip-7702/upgrade-eoa-to-7702-smart-account-wallet-signed.ts` around lines 88
- 95, Replace the raw viem sign() call used to set userOperation.signature with
the smartAccount helper: call smartAccount.signUserOperation(userOperation,
eoaDelegatorPrivateKey, chainId) and assign its result to
userOperation.signature (instead of using createUserOperationHash + sign()).
Keep createUserOperationHash only if still needed elsewhere, but ensure the
signature line uses smartAccount.signUserOperation with the same
eoaDelegatorPrivateKey and chainId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@passkeys/webauthn.ts`:
- Around line 175-190: The flags currently set using 0x40 + userVerificationFlag
can clear the UI (UP) bit when UV is required; change the logic to always OR the
UP bit (0x01) instead of replacing it—e.g., compute flags as 0x40 | 0x01 |
userVerificationFlag (or 0x41 | userVerificationFlag) when calling
buildAuthenticatorData in the registration path (where userVerification and
userVerificationFlag are used) and make the same change in the other occurrence
at lines 223-226 so UP is preserved for both registration and assertion.
- Around line 195-196: The rawId assignment currently uses
hexToBytes(credential.id) which returns a Uint8Array, causing a type mismatch
with PublicKeyCredential.rawId (ArrayBuffer); wrap the result with b2ab() to
convert to ArrayBuffer (mirror how
clientDataJSON/attestationObject/signature/authenticatorData are handled).
Update the rawId assignments (where hexToBytes(credential.id) is used) to
b2ab(hexToBytes(credential.id)) so rawId consistently matches the
PublicKeyCredential type.

In `@recovery/recovery.ts`:
- Around line 21-24: The fallback address and private key are chosen
independently, risking a mismatch; change the logic so either both
PUBLIC_ADDRESS and PRIVATE_KEY are used together or both are derived from the
same generated account (safeOwnerPrivateKey → safeOwner). Concretely, update the
ownerPublicAddress/ownerPrivateKey assignment to check if both
process.env.PUBLIC_ADDRESS and process.env.PRIVATE_KEY are present and use them
together, otherwise use safeOwner.address and safeOwnerPrivateKey (from
generatePrivateKey and privateKeyToAccount) so the signer and Safe owner always
match.

In `@simulate-with-tenderly/simulate-with-tenderly.ts`:
- Around line 17-20: The code currently allows PUBLIC_ADDRESS and PRIVATE_KEY to
be chosen independently which can mix a generated private key with an
env-specified address; instead ensure the owner key/address pair comes from a
single source: if PRIVATE_KEY is provided, derive owner (call
privateKeyToAccount) and set ownerPrivateKey and ownerPublicAddress from that
account; otherwise generate a new key with generatePrivateKey and derive its
address via privateKeyToAccount so ownerPrivateKeyGenerated, owner,
ownerPublicAddress and ownerPrivateKey remain consistent and never mixed from
different sources.

---

Outside diff comments:
In `@eip-7702/upgrade-eoa-to-7702-smart-account-wallet-signed.ts`:
- Around line 88-95: Replace the raw viem sign() call used to set
userOperation.signature with the smartAccount helper: call
smartAccount.signUserOperation(userOperation, eoaDelegatorPrivateKey, chainId)
and assign its result to userOperation.signature (instead of using
createUserOperationHash + sign()). Keep createUserOperationHash only if still
needed elsewhere, but ensure the signature line uses
smartAccount.signUserOperation with the same eoaDelegatorPrivateKey and chainId.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f51d067e-9cfb-4f9b-aa96-7da83a4482ad

📥 Commits

Reviewing files that changed from the base of the PR and between 173e04a and da189b5.

📒 Files selected for processing (12)
  • chain-abstraction/add-owner-passkey.ts
  • eip-7702/upgrade-eoa-to-7702-smart-account-sponsored-gas.ts
  • eip-7702/upgrade-eoa-to-7702-smart-account-wallet-signed.ts
  • multisig/multisig.ts
  • nested-safe-accounts/nested-safe-accounts.ts
  • passkeys/index.ts
  • passkeys/passkeys-v0.2.1.ts
  • passkeys/webauthn.ts
  • recovery/recovery.ts
  • simulate-with-tenderly/simulate-with-tenderly.ts
  • spend-permission/spend-permission.ts
  • tsconfig.json

Comment on lines +21 to +24
const safeOwnerPrivateKey = generatePrivateKey();
const safeOwner = privateKeyToAccount(safeOwnerPrivateKey);
const ownerPublicAddress = process.env.PUBLIC_ADDRESS || safeOwner.address as string
const ownerPrivateKey = process.env.PRIVATE_KEY || safeOwner.privateKey as string
const ownerPrivateKey = process.env.PRIVATE_KEY || safeOwnerPrivateKey as string
Copy link

@coderabbitai coderabbitai bot Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the fallback address and private key bound to the same account.

These values are selected independently. If only PUBLIC_ADDRESS or only PRIVATE_KEY is set, the Safe is initialized for one owner and signed by another, so the UserOperation will fail.

💡 Suggested fix
-    const safeOwnerPrivateKey = generatePrivateKey();
-    const safeOwner = privateKeyToAccount(safeOwnerPrivateKey);
-    const ownerPublicAddress = process.env.PUBLIC_ADDRESS || safeOwner.address as string
-    const ownerPrivateKey = process.env.PRIVATE_KEY || safeOwnerPrivateKey as string
+    const safeOwnerPrivateKey = generatePrivateKey();
+    const safeOwner = privateKeyToAccount(safeOwnerPrivateKey);
+    const envPublicAddress = process.env.PUBLIC_ADDRESS;
+    const envPrivateKey = process.env.PRIVATE_KEY;
+
+    if ((envPublicAddress && !envPrivateKey) || (!envPublicAddress && envPrivateKey)) {
+        throw new Error("PUBLIC_ADDRESS and PRIVATE_KEY must be provided together");
+    }
+
+    const ownerPublicAddress = envPublicAddress ?? safeOwner.address
+    const ownerPrivateKey = envPrivateKey ?? safeOwnerPrivateKey
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const safeOwnerPrivateKey = generatePrivateKey();
const safeOwner = privateKeyToAccount(safeOwnerPrivateKey);
const ownerPublicAddress = process.env.PUBLIC_ADDRESS || safeOwner.address as string
const ownerPrivateKey = process.env.PRIVATE_KEY || safeOwner.privateKey as string
const ownerPrivateKey = process.env.PRIVATE_KEY || safeOwnerPrivateKey as string
const safeOwnerPrivateKey = generatePrivateKey();
const safeOwner = privateKeyToAccount(safeOwnerPrivateKey);
const envPublicAddress = process.env.PUBLIC_ADDRESS;
const envPrivateKey = process.env.PRIVATE_KEY;
if ((envPublicAddress && !envPrivateKey) || (!envPublicAddress && envPrivateKey)) {
throw new Error("PUBLIC_ADDRESS and PRIVATE_KEY must be provided together");
}
const ownerPublicAddress = envPublicAddress ?? safeOwner.address
const ownerPrivateKey = envPrivateKey ?? safeOwnerPrivateKey
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@recovery/recovery.ts` around lines 21 - 24, The fallback address and private
key are chosen independently, risking a mismatch; change the logic so either
both PUBLIC_ADDRESS and PRIVATE_KEY are used together or both are derived from
the same generated account (safeOwnerPrivateKey → safeOwner). Concretely, update
the ownerPublicAddress/ownerPrivateKey assignment to check if both
process.env.PUBLIC_ADDRESS and process.env.PRIVATE_KEY are present and use them
together, otherwise use safeOwner.address and safeOwnerPrivateKey (from
generatePrivateKey and privateKeyToAccount) so the signer and Safe owner always
match.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix in another PR

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sednaoui, understood! Would you like me to open a GitHub issue to track this fix — ensuring PUBLIC_ADDRESS and PRIVATE_KEY are always used together (or both derived from the same generated account) in recovery/recovery.ts?


🧠 Learnings used
Learnt from: CR
Repo: candidelabs/abstractionkit-examples PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-25T15:03:59.652Z
Learning: Applies to **/*.ts : Initialize accounts using SafeAccountV0_3_0 class for most examples

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo candidelabs/abstractionkit-examples \
  --title "recovery/recovery.ts: Ensure PUBLIC_ADDRESS and PRIVATE_KEY are always used together" \
  --body "## Summary

In \`recovery/recovery.ts\`, the fallback \`ownerPublicAddress\` and \`ownerPrivateKey\` are selected independently from environment variables. If only \`PUBLIC_ADDRESS\` or only \`PRIVATE_KEY\` is set, the Safe is initialized for one owner but signed by another, causing the UserOperation to fail.

## Suggested Fix

Validate that both \`PUBLIC_ADDRESS\` and \`PRIVATE_KEY\` are provided together, or derive both from the same generated account:

\`\`\`ts
const safeOwnerPrivateKey = generatePrivateKey();
const safeOwner = privateKeyToAccount(safeOwnerPrivateKey);
const envPublicAddress = process.env.PUBLIC_ADDRESS;
const envPrivateKey = process.env.PRIVATE_KEY;

if ((envPublicAddress && !envPrivateKey) || (!envPublicAddress && envPrivateKey)) {
    throw new Error(\"PUBLIC_ADDRESS and PRIVATE_KEY must be provided together\");
}

const ownerPublicAddress = envPublicAddress ?? safeOwner.address
const ownerPrivateKey = envPrivateKey ?? safeOwnerPrivateKey
\`\`\`

## References

- PR: https://github.com/candidelabs/abstractionkit-examples/pull/15
- Review comment: https://github.com/candidelabs/abstractionkit-examples/pull/15#discussion_r2899597098

Requested by `@Sednaoui`" \
  --assignee Sednaoui

Length of output: 64


@Sednaoui, done! I've opened GitHub issue #16 to track the fix for ensuring PUBLIC_ADDRESS and PRIVATE_KEY are always used together (or both derived from the same generated account) in recovery/recovery.ts.


🧠 Learnings used
Learnt from: CR
Repo: candidelabs/abstractionkit-examples PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-25T15:03:59.652Z
Learning: Applies to **/*.ts : Initialize accounts using SafeAccountV0_3_0 class for most examples

Comment on lines +17 to +20
const ownerPrivateKeyGenerated = generatePrivateKey();
const owner = privateKeyToAccount(ownerPrivateKeyGenerated);
const ownerPublicAddress = process.env.PUBLIC_ADDRESS || owner.address as string
const ownerPrivateKey = process.env.PRIVATE_KEY || owner.privateKey as string
const ownerPrivateKey = process.env.PRIVATE_KEY || ownerPrivateKeyGenerated as string
Copy link

@coderabbitai coderabbitai bot Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not resolve PUBLIC_ADDRESS and PRIVATE_KEY independently.

This can mix an env address with a generated private key, or vice versa. In that state the example signs for a different owner than the account it initializes, and the simulated UserOperation becomes invalid.

💡 Suggested fix
-    const ownerPrivateKeyGenerated = generatePrivateKey();
-    const owner = privateKeyToAccount(ownerPrivateKeyGenerated);
-    const ownerPublicAddress = process.env.PUBLIC_ADDRESS || owner.address as string
-    const ownerPrivateKey = process.env.PRIVATE_KEY || ownerPrivateKeyGenerated as string
+    const ownerPrivateKeyGenerated = generatePrivateKey();
+    const owner = privateKeyToAccount(ownerPrivateKeyGenerated);
+    const envPublicAddress = process.env.PUBLIC_ADDRESS;
+    const envPrivateKey = process.env.PRIVATE_KEY;
+
+    if ((envPublicAddress && !envPrivateKey) || (!envPublicAddress && envPrivateKey)) {
+        throw new Error("PUBLIC_ADDRESS and PRIVATE_KEY must be provided together");
+    }
+
+    const ownerPublicAddress = envPublicAddress ?? owner.address
+    const ownerPrivateKey = envPrivateKey ?? ownerPrivateKeyGenerated
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ownerPrivateKeyGenerated = generatePrivateKey();
const owner = privateKeyToAccount(ownerPrivateKeyGenerated);
const ownerPublicAddress = process.env.PUBLIC_ADDRESS || owner.address as string
const ownerPrivateKey = process.env.PRIVATE_KEY || owner.privateKey as string
const ownerPrivateKey = process.env.PRIVATE_KEY || ownerPrivateKeyGenerated as string
const ownerPrivateKeyGenerated = generatePrivateKey();
const owner = privateKeyToAccount(ownerPrivateKeyGenerated);
const envPublicAddress = process.env.PUBLIC_ADDRESS;
const envPrivateKey = process.env.PRIVATE_KEY;
if ((envPublicAddress && !envPrivateKey) || (!envPublicAddress && envPrivateKey)) {
throw new Error("PUBLIC_ADDRESS and PRIVATE_KEY must be provided together");
}
const ownerPublicAddress = envPublicAddress ?? owner.address
const ownerPrivateKey = envPrivateKey ?? ownerPrivateKeyGenerated
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@simulate-with-tenderly/simulate-with-tenderly.ts` around lines 17 - 20, The
code currently allows PUBLIC_ADDRESS and PRIVATE_KEY to be chosen independently
which can mix a generated private key with an env-specified address; instead
ensure the owner key/address pair comes from a single source: if PRIVATE_KEY is
provided, derive owner (call privateKeyToAccount) and set ownerPrivateKey and
ownerPublicAddress from that account; otherwise generate a new key with
generatePrivateKey and derive its address via privateKeyToAccount so
ownerPrivateKeyGenerated, owner, ownerPublicAddress and ownerPrivateKey remain
consistent and never mixed from different sources.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix in another PR

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sednaoui, understood! Would you like me to open a GitHub issue to track this fix so it doesn't get lost?


🧠 Learnings used
Learnt from: CR
Repo: candidelabs/abstractionkit-examples PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-25T15:03:59.652Z
Learning: Use Tenderly simulation for debugging transaction failures via simulate-with-tenderly example

Learnt from: CR
Repo: candidelabs/abstractionkit-examples PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-25T15:03:59.652Z
Learning: Applies to **/*.ts : Always sign UserOperations with signUserOperation() method, passing privateKey array and chainId

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo candidelabs/abstractionkit-examples \
  --title "Fix: Ensure PUBLIC_ADDRESS and PRIVATE_KEY are resolved from the same source in simulate-with-tenderly" \
  --assignee Sednaoui \
  --body "## Summary

In \`simulate-with-tenderly/simulate-with-tenderly.ts\`, \`PUBLIC_ADDRESS\` and \`PRIVATE_KEY\` environment variables are currently resolved independently. This can result in a mismatch where an env-specified address is paired with a generated private key, or vice versa — causing the simulated UserOperation to be signed by a different owner than the one used to initialize the account, making it invalid.

## Steps to Reproduce

1. Set only \`PUBLIC_ADDRESS\` in the environment (without \`PRIVATE_KEY\`).
2. Run the simulate-with-tenderly example.
3. The account is initialized with the env address, but the UserOperation is signed with a freshly generated private key — the simulation will fail or produce an invalid result.

## Expected Behavior

\`ownerPublicAddress\` and \`ownerPrivateKey\` must always come from the same source (either both from environment variables, or both from a freshly generated key pair). If only one of the two env vars is provided, an error should be thrown.

## Suggested Fix

\`\`\`ts
const ownerPrivateKeyGenerated = generatePrivateKey();
const owner = privateKeyToAccount(ownerPrivateKeyGenerated);
const envPublicAddress = process.env.PUBLIC_ADDRESS;
const envPrivateKey = process.env.PRIVATE_KEY;

if ((envPublicAddress && !envPrivateKey) || (!envPublicAddress && envPrivateKey)) {
    throw new Error(\"PUBLIC_ADDRESS and PRIVATE_KEY must be provided together\");
}

const ownerPublicAddress = envPublicAddress ?? owner.address;
const ownerPrivateKey = envPrivateKey ?? ownerPrivateKeyGenerated;
\`\`\`

## References

- PR: https://github.com/candidelabs/abstractionkit-examples/pull/15
- Review comment: https://github.com/candidelabs/abstractionkit-examples/pull/15#discussion_r2899597100

Requested by `@Sednaoui`"

Length of output: 64


@Sednaoui, I've opened a GitHub issue to track this fix: #17

It's assigned to you and includes a summary of the problem, steps to reproduce, and the suggested fix for reference.


🧠 Learnings used
Learnt from: CR
Repo: candidelabs/abstractionkit-examples PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-25T15:03:59.652Z
Learning: Use Tenderly simulation for debugging transaction failures via simulate-with-tenderly example

Learnt from: CR
Repo: candidelabs/abstractionkit-examples PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-25T15:03:59.652Z
Learning: Applies to **/*.ts : Always sign UserOperations with signUserOperation() method, passing privateKey array and chainId

Always set UP (User Present) bit in authenticator data flags per the
WebAuthn spec. Previously when UV was required, UP was cleared (0x44
instead of 0x45). This was inherited from the original Safe repo code.

Wrap rawId with b2ab() to return ArrayBuffer matching the
PublicKeyCredential interface, consistent with all other ArrayBuffer
fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@passkeys/webauthn.ts`:
- Around line 233-241: The returned response sets response.userHandle to
credential.user (a Uint8Array) but AuthenticatorAssertionResponse expects an
ArrayBuffer; wrap credential.user with b2ab() like the other binary fields to
convert it to ArrayBuffer. Update the response construction in the function that
returns id/rawId/response (reference symbols: response.userHandle,
credential.user, b2ab, AuthenticatorAssertionResponse) so userHandle is
b2ab(credential.user) for type consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 242c5e0d-c8bd-4c23-83ac-3f7fdecd13a5

📥 Commits

Reviewing files that changed from the base of the PR and between da189b5 and 89a9f39.

📒 Files selected for processing (1)
  • passkeys/webauthn.ts

Copy link

@itxtoledo itxtoledo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants