From 6f4a79620f1d96281bbf9dc580a00e9cf3e18a62 Mon Sep 17 00:00:00 2001 From: joelorzet Date: Wed, 6 May 2026 09:53:53 -0300 Subject: [PATCH] chore(wallet): KEEP-230 remove Para SDK, schema columns, and supporting code Para signing was retired in PR #983; this follow-up removes the now-dead surface area so Turnkey is the only wallet path. - Drop @getpara/ethers-v6-integration and @getpara/server-sdk from package.json + pnpm-lock; delete lib/para/, lib/encryption.ts (Para MPC user-share helpers), and Para spec docs. - Replace the Para-backed viem LocalAccount used by sponsored-client.ts with a Turnkey-backed adapter (lib/web3/turnkey-viem-account.ts) that signs through the Turnkey API client. Move org-scoped wallet helpers out of lib/para/ to lib/web3/wallet-helpers.ts and drop the deprecated user-id-keyed lookups. - Delete Para-only API routes (refresh-share, share) and strip the provider="turnkey" filter from export-key/verify since provider is going away. - Drop the Para columns (provider, para_wallet_id, user_share) and rename para_wallets -> organization_wallets via migration 0069. Update Drizzle schema, relations, and the prometheus / db-metrics collectors to drop the Para gauge and provider split. - Remove Para test infrastructure: delete the eip7702-spike integration test, the Para portal cleanup logic in the Playwright e2e helpers, and the seed-test-wallet / fund-test-wallet scripts. Update transaction-flow + write-contract-workflow tests to skip on missing Turnkey env (instead of missing PARA_*). - Strip PARA_* env vars from staging / prod / pr-environment helm values, the e2e GitHub workflow, and the setup-e2e-db action. --- .github/actions/setup-e2e-db/action.yml | 14 - .github/workflows/deploy-pr-environment.yaml | 4 - .github/workflows/e2e-tests-ephemeral.yml | 14 - app/api/execute/_lib/wallet-check.ts | 2 +- app/api/gas/estimate/route.ts | 2 +- app/api/mcp/schemas/route.ts | 13 +- app/api/user/route.ts | 16 +- app/api/user/wallet/active/route.ts | 3 +- app/api/user/wallet/balances/route.ts | 7 +- app/api/user/wallet/estimate-gas/route.ts | 2 +- .../user/wallet/export-key/request/route.ts | 11 +- .../user/wallet/export-key/verify/route.ts | 8 +- app/api/user/wallet/refresh-share/route.ts | 89 --- app/api/user/wallet/route.ts | 19 +- app/api/user/wallet/share/route.ts | 60 -- app/api/user/wallet/tokens/route.ts | 2 +- app/api/user/wallet/withdraw/route.ts | 21 +- deploy/keeperhub/prod/values.yaml | 7 - deploy/keeperhub/staging/values.yaml | 7 - deploy/pr-environment/values.template.yaml | 23 - drizzle/0069_keep_230_decom_para.sql | 19 + drizzle/meta/_journal.json | 7 + drizzle/relations.ts | 12 +- drizzle/schema.ts | 10 +- keeperhub-executor/startup-checks.ts | 9 +- lib/db/integrations.ts | 8 +- lib/db/schema-extensions.ts | 22 +- lib/db/schema.ts | 7 +- lib/encryption.ts | 69 -- lib/metrics/METRICS_REFERENCE.md | 14 +- lib/metrics/collectors/prometheus.ts | 27 +- lib/metrics/db-metrics.ts | 36 +- lib/para/viem-account-adapter.ts | 167 ---- lib/web3/sponsored-client.ts | 6 +- lib/web3/transaction-manager.ts | 2 +- lib/web3/turnkey-viem-account.ts | 167 ++++ lib/{para => web3}/wallet-helpers.ts | 66 -- package.json | 4 - plugins/web3/index.ts | 4 +- plugins/web3/steps/approve-token-core.ts | 4 +- plugins/web3/steps/transfer-funds-core.ts | 2 +- plugins/web3/steps/transfer-token-core.ts | 4 +- plugins/web3/steps/write-contract-core.ts | 4 +- pnpm-lock.yaml | 732 ++---------------- scripts/miscellaneous/fund-test-wallet.ts | 142 ---- scripts/seed/seed-test-wallet.ts | 293 ------- scripts/seed/seed-x402-test.ts | 9 +- specs/web3/PARA_INTEGRATION.md | 90 --- specs/web3/PARA_WORKFLOW_STEP.md | 44 -- tests/e2e/playwright/utils/cleanup.ts | 92 +-- tests/e2e/vitest/transaction-flow.test.ts | 365 +-------- .../vitest/write-contract-workflow.test.ts | 12 +- tests/integration/eip7702-spike.d.ts | 8 - tests/integration/eip7702-spike.test.ts | 584 -------------- tests/integration/user-route.test.ts | 12 +- tests/integration/web3-steps.test.ts | 2 +- tests/unit/approve-token.test.ts | 2 +- tests/unit/ensure-wallet-integration.test.ts | 2 +- tests/unit/sponsored-client.test.ts | 4 +- tests/unit/write-contract-core.test.ts | 10 +- tests/unit/write-failover.test.ts | 2 +- 61 files changed, 401 insertions(+), 2998 deletions(-) delete mode 100644 app/api/user/wallet/refresh-share/route.ts delete mode 100644 app/api/user/wallet/share/route.ts create mode 100644 drizzle/0069_keep_230_decom_para.sql delete mode 100644 lib/encryption.ts delete mode 100644 lib/para/viem-account-adapter.ts create mode 100644 lib/web3/turnkey-viem-account.ts rename lib/{para => web3}/wallet-helpers.ts (59%) delete mode 100644 scripts/miscellaneous/fund-test-wallet.ts delete mode 100644 scripts/seed/seed-test-wallet.ts delete mode 100644 specs/web3/PARA_INTEGRATION.md delete mode 100644 specs/web3/PARA_WORKFLOW_STEP.md delete mode 100644 tests/integration/eip7702-spike.d.ts delete mode 100644 tests/integration/eip7702-spike.test.ts diff --git a/.github/actions/setup-e2e-db/action.yml b/.github/actions/setup-e2e-db/action.yml index 18d8f098a..835c09f8f 100644 --- a/.github/actions/setup-e2e-db/action.yml +++ b/.github/actions/setup-e2e-db/action.yml @@ -5,12 +5,6 @@ inputs: database_url: description: 'PostgreSQL connection string' required: true - test_para_user_share: - description: 'TEST_PARA_USER_SHARE secret for wallet seeding' - required: true - wallet_encryption_key: - description: 'WALLET_ENCRYPTION_KEY secret for wallet seeding' - required: true chain_rpc_config: description: 'CHAIN_RPC_CONFIG for RPC endpoint resolution' required: false @@ -37,11 +31,3 @@ runs: DATABASE_URL: ${{ inputs.database_url }} CHAIN_RPC_CONFIG: ${{ inputs.chain_rpc_config }} run: pnpm db:seed - - - name: Seed test wallet - shell: bash - env: - DATABASE_URL: ${{ inputs.database_url }} - TEST_PARA_USER_SHARE: ${{ inputs.test_para_user_share }} - WALLET_ENCRYPTION_KEY: ${{ inputs.wallet_encryption_key }} - run: pnpm db:seed-test-wallet diff --git a/.github/workflows/deploy-pr-environment.yaml b/.github/workflows/deploy-pr-environment.yaml index 88f035018..5ce89e699 100644 --- a/.github/workflows/deploy-pr-environment.yaml +++ b/.github/workflows/deploy-pr-environment.yaml @@ -393,7 +393,6 @@ jobs: IMAGE_TAG: ${{ steps.vars.outputs.sha_short }} DB_PASSWORD: ${{ steps.db-password.outputs.password }} SERVICE_ACCOUNT_ROLE_ARN: ${{ vars.TO_SERVICE_ACCOUNT_ROLE_ARN }} - TEST_PARA_USER_SHARE: ${{ secrets.TEST_PARA_USER_SHARE }} # PR-specific events image tag if deploy-pr-events label was set # (build-events-image job ran). Otherwise stays on the staging # event-latest tag. @@ -736,9 +735,6 @@ jobs: --exclude '**/write-contract-workflow.test.ts' env: KEEPERHUB_URL: ${{ env.KEEPERHUB_URL }} - WALLET_ENCRYPTION_KEY: ${{ secrets.TEST_WALLET_ENCRYPTION_KEY }} - PARA_API_KEY: ${{ secrets.TEST_PARA_API_KEY }} - PARA_ENVIRONMENT: beta WORKFLOW_TARGET_WORLD: "@workflow/world-postgres" AWS_ACCESS_KEY_ID: test AWS_SECRET_ACCESS_KEY: test diff --git a/.github/workflows/e2e-tests-ephemeral.yml b/.github/workflows/e2e-tests-ephemeral.yml index 073a6a3b3..c8fc83c4f 100644 --- a/.github/workflows/e2e-tests-ephemeral.yml +++ b/.github/workflows/e2e-tests-ephemeral.yml @@ -181,8 +181,6 @@ jobs: uses: ./.github/actions/setup-e2e-db with: database_url: postgresql://postgres:postgres@localhost:5432/keeperhub_test - test_para_user_share: ${{ secrets.TEST_PARA_USER_SHARE }} - wallet_encryption_key: ${{ secrets.TEST_WALLET_ENCRYPTION_KEY }} chain_rpc_config: ${{ secrets.CHAIN_RPC_CONFIG }} - name: Start application @@ -205,9 +203,6 @@ jobs: env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/keeperhub_test BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }} - WALLET_ENCRYPTION_KEY: ${{ secrets.TEST_WALLET_ENCRYPTION_KEY }} - PARA_API_KEY: ${{ secrets.TEST_PARA_API_KEY }} - PARA_ENVIRONMENT: beta WORKFLOW_TARGET_WORLD: "@workflow/world-postgres" AWS_ENDPOINT_URL: http://localhost:4566 AWS_ACCESS_KEY_ID: test @@ -274,8 +269,6 @@ jobs: uses: ./.github/actions/setup-e2e-db with: database_url: postgresql://postgres:postgres@localhost:5432/keeperhub_test - test_para_user_share: ${{ secrets.TEST_PARA_USER_SHARE }} - wallet_encryption_key: ${{ secrets.TEST_WALLET_ENCRYPTION_KEY }} chain_rpc_config: ${{ secrets.CHAIN_RPC_CONFIG }} - name: Start application @@ -298,14 +291,7 @@ jobs: env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/keeperhub_test BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }} - PARA_API_KEY: ${{ secrets.TEST_PARA_API_KEY }} - PARA_ENVIRONMENT: beta - WALLET_ENCRYPTION_KEY: ${{ secrets.TEST_WALLET_ENCRYPTION_KEY }} INTEGRATION_ENCRYPTION_KEY: ${{ secrets.TEST_INTEGRATION_ENCRYPTION_KEY }} - PARA_PORTAL_ORG_ID: ${{ secrets.PARA_PORTAL_ORG_ID }} - PARA_PORTAL_PROJECT_ID: ${{ secrets.PARA_PORTAL_PROJECT_ID }} - PARA_PORTAL_KEY_ID: ${{ secrets.PARA_PORTAL_KEY_ID }} - PARA_PORTAL_API_KEY: ${{ secrets.PARA_PORTAL_API_KEY }} CI: true TEST_API_KEY: ${{ secrets.TEST_API_KEY }} # Start application step above runs `pnpm build && pnpm start` diff --git a/app/api/execute/_lib/wallet-check.ts b/app/api/execute/_lib/wallet-check.ts index 757528e87..f4724cc20 100644 --- a/app/api/execute/_lib/wallet-check.ts +++ b/app/api/execute/_lib/wallet-check.ts @@ -1,7 +1,7 @@ import "server-only"; import { NextResponse } from "next/server"; -import { organizationHasWallet } from "@/lib/para/wallet-helpers"; +import { organizationHasWallet } from "@/lib/web3/wallet-helpers"; /** * Check if the organization has a wallet configured. diff --git a/app/api/gas/estimate/route.ts b/app/api/gas/estimate/route.ts index c46222a0c..707c6a2a1 100644 --- a/app/api/gas/estimate/route.ts +++ b/app/api/gas/estimate/route.ts @@ -3,9 +3,9 @@ import { NextResponse } from "next/server"; import { apiError } from "@/lib/api-error"; import ERC20_ABI from "@/lib/contracts/abis/erc20.json"; import { resolveOrganizationId } from "@/lib/middleware/auth-helpers"; -import { getOrganizationWalletAddress } from "@/lib/para/wallet-helpers"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { getChainGasDefaults } from "@/lib/web3/gas-defaults"; +import { getOrganizationWalletAddress } from "@/lib/web3/wallet-helpers"; type EstimateConfig = { contractAddress?: string; diff --git a/app/api/mcp/schemas/route.ts b/app/api/mcp/schemas/route.ts index 4504364e5..6a38fb900 100644 --- a/app/api/mcp/schemas/route.ts +++ b/app/api/mcp/schemas/route.ts @@ -1,11 +1,12 @@ import { eq } from "drizzle-orm"; import { NextResponse } from "next/server"; - -import { BUILTIN_NODE_ID, BUILTIN_NODE_LABEL } from "@/lib/workflow/editor/builtin-variables"; - import { db } from "@/lib/db"; import { chains, explorerConfigs } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; +import { + BUILTIN_NODE_ID, + BUILTIN_NODE_LABEL, +} from "@/lib/workflow/editor/builtin-variables"; import { type ActionConfigFieldBase, computeActionId, @@ -366,10 +367,10 @@ function derivePlatformCapabilities(plugins: IntegrationPlugin[]) { return { wallet: web3Plugin ? { - provider: "Para", - features: ["mpc", "non-custodial", "hosted"], + provider: "Turnkey", + features: ["secure-enclave", "non-custodial", "hosted"], description: - "Para MPC wallet - keys are split between user and Para, neither party can sign alone", + "Turnkey wallet backed by hardware secure enclaves; KeeperHub signs transactions on the user's behalf via the Turnkey API", } : null, proxyContracts: hasAbiAutoFetch diff --git a/app/api/user/route.ts b/app/api/user/route.ts index 2b98403c7..11205a359 100644 --- a/app/api/user/route.ts +++ b/app/api/user/route.ts @@ -9,7 +9,7 @@ import { type DualAuthContext, getDualAuthContext, } from "@/lib/middleware/auth-helpers"; -import { getUserWallet } from "@/lib/para/wallet-helpers"; +import { getOrganizationWallet } from "@/lib/web3/wallet-helpers"; export async function GET(request: Request): Promise { let authContext: DualAuthContext | null = null; @@ -22,7 +22,7 @@ export async function GET(request: Request): Promise { ); } - const { userId } = authContext; + const { userId, organizationId } = authContext; if (!userId) { return NextResponse.json( { error: "Auth context missing user. Please recreate the API key." }, @@ -53,11 +53,13 @@ export async function GET(request: Request): Promise { }); let walletAddress: string | null = null; - try { - const wallet = await getUserWallet(userId); - walletAddress = wallet.walletAddress; - } catch { - walletAddress = null; + if (organizationId) { + try { + const wallet = await getOrganizationWallet(organizationId); + walletAddress = wallet.walletAddress; + } catch { + walletAddress = null; + } } return NextResponse.json({ diff --git a/app/api/user/wallet/active/route.ts b/app/api/user/wallet/active/route.ts index 270cf6944..607f8d70e 100644 --- a/app/api/user/wallet/active/route.ts +++ b/app/api/user/wallet/active/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from "next/server"; // Switching the active wallet is disabled. Every organization signs with its -// Turnkey wallet; the Para → Turnkey migration is one-way and no longer -// user-driven. Kept as a 410 so old clients get a clear, non-generic error +// Turnkey wallet. Kept as a 410 so old clients get a clear, non-generic error // instead of a silent 404. export function POST(): NextResponse { return NextResponse.json( diff --git a/app/api/user/wallet/balances/route.ts b/app/api/user/wallet/balances/route.ts index 1dbeebd4c..a58f47cee 100644 --- a/app/api/user/wallet/balances/route.ts +++ b/app/api/user/wallet/balances/route.ts @@ -12,9 +12,9 @@ import { } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; import { resolveOrganizationId } from "@/lib/middleware/auth-helpers"; -import { getOrganizationWallet } from "@/lib/para/wallet-helpers"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { formatWeiToBalance } from "@/lib/wallet/fetch-balances"; +import { getOrganizationWallet } from "@/lib/web3/wallet-helpers"; const NATIVE_DECIMALS = 18; @@ -123,10 +123,7 @@ export async function GET(request: Request) { db.select().from(explorerConfigs), ]); - const supportedTokensByChain = new Map< - number, - typeof allSupportedTokens - >(); + const supportedTokensByChain = new Map(); for (const token of allSupportedTokens) { const existing = supportedTokensByChain.get(token.chainId) ?? []; existing.push(token); diff --git a/app/api/user/wallet/estimate-gas/route.ts b/app/api/user/wallet/estimate-gas/route.ts index 9d3398fbf..744c8c022 100644 --- a/app/api/user/wallet/estimate-gas/route.ts +++ b/app/api/user/wallet/estimate-gas/route.ts @@ -8,9 +8,9 @@ import { db } from "@/lib/db"; import { chains } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; import { getActiveOrgId } from "@/lib/middleware/org-context"; -import { getOrganizationWalletAddress } from "@/lib/para/wallet-helpers"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { getGasStrategy } from "@/lib/web3/gas-strategy"; +import { getOrganizationWalletAddress } from "@/lib/web3/wallet-helpers"; const ERC20_TRANSFER_ABI = [ "function transfer(address to, uint256 amount) returns (bool)", diff --git a/app/api/user/wallet/export-key/request/route.ts b/app/api/user/wallet/export-key/request/route.ts index 7efbeef5a..b1c211458 100644 --- a/app/api/user/wallet/export-key/request/route.ts +++ b/app/api/user/wallet/export-key/request/route.ts @@ -1,5 +1,5 @@ import crypto from "node:crypto"; -import { and, eq } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { headers } from "next/headers"; import { NextResponse } from "next/server"; import { apiError } from "@/lib/api-error"; @@ -63,8 +63,6 @@ export async function POST(request: Request): Promise { ); } - // Verify a Turnkey wallet exists (only Turnkey wallets are exportable; - // Para wallets during migration are inactive and not exportable here) const turnkeyWallets = await db .select({ id: organizationWallets.id, @@ -72,12 +70,7 @@ export async function POST(request: Request): Promise { email: organizationWallets.email, }) .from(organizationWallets) - .where( - and( - eq(organizationWallets.organizationId, activeOrgId), - eq(organizationWallets.provider, "turnkey") - ) - ) + .where(eq(organizationWallets.organizationId, activeOrgId)) .limit(1); if (turnkeyWallets.length === 0) { diff --git a/app/api/user/wallet/export-key/verify/route.ts b/app/api/user/wallet/export-key/verify/route.ts index 4d921ed4f..c15bbd2ac 100644 --- a/app/api/user/wallet/export-key/verify/route.ts +++ b/app/api/user/wallet/export-key/verify/route.ts @@ -132,16 +132,10 @@ export async function POST(request: Request): Promise { // Delete used code (single-use) await db.delete(keyExportCodes).where(eq(keyExportCodes.id, storedCode.id)); - // Fetch Turnkey wallet and export const wallets = await db .select() .from(organizationWallets) - .where( - and( - eq(organizationWallets.organizationId, activeOrgId), - eq(organizationWallets.provider, "turnkey") - ) - ) + .where(eq(organizationWallets.organizationId, activeOrgId)) .limit(1); if (wallets.length === 0) { diff --git a/app/api/user/wallet/refresh-share/route.ts b/app/api/user/wallet/refresh-share/route.ts deleted file mode 100644 index 42b613c2d..000000000 --- a/app/api/user/wallet/refresh-share/route.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { eq } from "drizzle-orm"; -import { headers } from "next/headers"; -import { NextResponse } from "next/server"; -import { apiError } from "@/lib/api-error"; -import { auth } from "@/lib/auth"; -import { db } from "@/lib/db"; -import { paraWallets } from "@/lib/db/schema"; -import { encryptUserShare } from "@/lib/encryption"; -import { getActiveOrgId } from "@/lib/middleware/org-context"; - -export async function POST(request: Request): Promise { - try { - const session = await auth.api.getSession({ - headers: request.headers, - }); - - if (!session?.user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const activeOrgId = getActiveOrgId(session); - if (!activeOrgId) { - return NextResponse.json( - { error: "No active organization" }, - { status: 400 } - ); - } - - const activeMember = await auth.api.getActiveMember({ - headers: await headers(), - }); - - if (!activeMember) { - return NextResponse.json( - { error: "You are not a member of the active organization" }, - { status: 403 } - ); - } - - if (activeMember.role !== "admin" && activeMember.role !== "owner") { - return NextResponse.json( - { error: "Only admins and owners can refresh wallet shares" }, - { status: 403 } - ); - } - - const body: { userShare?: string } = await request.json(); - const { userShare } = body; - - if (!userShare || typeof userShare !== "string") { - return NextResponse.json( - { error: "userShare is required" }, - { status: 400 } - ); - } - - const MIN_USER_SHARE_LENGTH = 100; - const MAX_USER_SHARE_LENGTH = 10_000; - - if ( - userShare.length < MIN_USER_SHARE_LENGTH || - userShare.length > MAX_USER_SHARE_LENGTH - ) { - return NextResponse.json( - { error: "Invalid userShare: unexpected length" }, - { status: 400 } - ); - } - - const encryptedShare = encryptUserShare(userShare); - - const updated = await db - .update(paraWallets) - .set({ userShare: encryptedShare }) - .where(eq(paraWallets.organizationId, activeOrgId)) - .returning({ id: paraWallets.id }); - - if (updated.length === 0) { - return NextResponse.json( - { error: "No wallet found for this organization" }, - { status: 404 } - ); - } - - return NextResponse.json({ success: true }); - } catch (error) { - return apiError(error, "Failed to refresh wallet share"); - } -} diff --git a/app/api/user/wallet/route.ts b/app/api/user/wallet/route.ts index b65eb479c..02d1ef288 100644 --- a/app/api/user/wallet/route.ts +++ b/app/api/user/wallet/route.ts @@ -12,8 +12,8 @@ import { import { integrations, organizationWallets } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; import { - type DualAuthContext, auditFromAuth, + type DualAuthContext, getDualAuthContext, } from "@/lib/middleware/auth-helpers"; import { getActiveOrgId } from "@/lib/middleware/org-context"; @@ -84,8 +84,7 @@ async function validateUserAndOrganization(request: Request) { return { user, organizationId: activeOrgId, member: activeMember }; } -// Helper: Check if a wallet already exists for this organization (one wallet per org at creation time; -// Para → Turnkey dual state is only reached via the provisioning script, not via this endpoint). +// Helper: Check if a wallet already exists for this organization. async function checkExistingWallet( organizationId: string ): Promise<{ error: string; status: number } | { valid: true }> { @@ -183,7 +182,6 @@ async function storeTurnkeyWalletAndIntegration(options: { await db.insert(organizationWallets).values({ userId, organizationId, - provider: "turnkey", email, walletAddress: normalizedWalletAddress, turnkeySubOrgId, @@ -192,7 +190,11 @@ async function storeTurnkeyWalletAndIntegration(options: { }); await createIntegration( - buildWalletIntegrationPayload(userId, organizationId, normalizedWalletAddress) + buildWalletIntegrationPayload( + userId, + organizationId, + normalizedWalletAddress + ) ); return { walletAddress: normalizedWalletAddress, walletId: turnkeyWalletId }; @@ -217,9 +219,6 @@ export async function GET(request: Request): Promise { ); } - // Turnkey is the only signer. Inactive Para rows may still exist in the - // DB for historical reasons but are not returned: the org has a single - // wallet surface, full stop. const active = await db .select() .from(organizationWallets) @@ -323,7 +322,6 @@ export async function POST(request: Request) { walletId, email: walletEmail, organizationId, - provider: "turnkey", }, }); } catch (error) { @@ -343,9 +341,6 @@ export async function DELETE(request: Request) { } const { organizationId } = validation; - // 2. Delete only the active wallet for this organization. - // During Para → Turnkey migration both wallets may coexist; the inactive - // Para row must be removed via a dedicated admin flow (follow-up ticket). const deletedWallet = await db .delete(organizationWallets) .where( diff --git a/app/api/user/wallet/share/route.ts b/app/api/user/wallet/share/route.ts deleted file mode 100644 index 200ebbc7b..000000000 --- a/app/api/user/wallet/share/route.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { headers } from "next/headers"; -import { NextResponse } from "next/server"; -import { apiError } from "@/lib/api-error"; -import { auth } from "@/lib/auth"; -import { decryptUserShare } from "@/lib/encryption"; -import { getActiveOrgId } from "@/lib/middleware/org-context"; -import { getOrganizationWallet } from "@/lib/para/wallet-helpers"; - -export async function GET(request: Request): Promise { - try { - const session = await auth.api.getSession({ - headers: request.headers, - }); - - if (!session?.user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const activeOrgId = getActiveOrgId(session); - if (!activeOrgId) { - return NextResponse.json( - { error: "No active organization" }, - { status: 400 } - ); - } - - const activeMember = await auth.api.getActiveMember({ - headers: await headers(), - }); - - if (!activeMember) { - return NextResponse.json( - { error: "You are not a member of the active organization" }, - { status: 403 } - ); - } - - if (activeMember.role !== "admin" && activeMember.role !== "owner") { - return NextResponse.json( - { error: "Only admins and owners can access wallet shares" }, - { status: 403 } - ); - } - - const wallet = await getOrganizationWallet(activeOrgId); - - if (!wallet.userShare) { - return NextResponse.json( - { error: "This wallet does not have a user share (non-Para wallet)" }, - { status: 400 } - ); - } - - const decryptedShare = decryptUserShare(wallet.userShare); - - return NextResponse.json({ userShare: decryptedShare }); - } catch (error) { - return apiError(error, "Failed to get wallet share"); - } -} diff --git a/app/api/user/wallet/tokens/route.ts b/app/api/user/wallet/tokens/route.ts index 0850a74ff..83eb707e9 100644 --- a/app/api/user/wallet/tokens/route.ts +++ b/app/api/user/wallet/tokens/route.ts @@ -8,8 +8,8 @@ import { db } from "@/lib/db"; import { chains, organizationTokens, supportedTokens } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; import { resolveOrganizationId } from "@/lib/middleware/auth-helpers"; -import { organizationHasWallet } from "@/lib/para/wallet-helpers"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; +import { organizationHasWallet } from "@/lib/web3/wallet-helpers"; /** * GET /api/user/wallet/tokens diff --git a/app/api/user/wallet/withdraw/route.ts b/app/api/user/wallet/withdraw/route.ts index d7e94a694..bd0ab8a58 100644 --- a/app/api/user/wallet/withdraw/route.ts +++ b/app/api/user/wallet/withdraw/route.ts @@ -8,16 +8,16 @@ import { db } from "@/lib/db"; import { chains } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; import { getActiveOrgId } from "@/lib/middleware/org-context"; -import { - getOrganizationWalletAddress, - initializeWalletSigner, -} from "@/lib/para/wallet-helpers"; import { getGasStrategy } from "@/lib/web3/gas-strategy"; import { getNonceManager } from "@/lib/web3/nonce-manager"; import { type TransactionContext, withNonceSession, } from "@/lib/web3/transaction-manager"; +import { + getOrganizationWalletAddress, + initializeWalletSigner, +} from "@/lib/web3/wallet-helpers"; const ERC20_TRANSFER_ABI = [ "function transfer(address to, uint256 amount) returns (bool)", @@ -183,10 +183,7 @@ export async function POST(request: Request) { const chainId = Number.parseInt(String(rawChainId), 10); if (Number.isNaN(chainId)) { - return NextResponse.json( - { error: "Invalid chainId" }, - { status: 400 } - ); + return NextResponse.json({ error: "Invalid chainId" }, { status: 400 }); } // Validate recipient address @@ -244,11 +241,15 @@ export async function POST(request: Request) { const nonceManager = getNonceManager(); const gasStrategy = getGasStrategy(); - // Initialize Para signer + // Initialize wallet signer console.log( `[Withdraw] Initializing signer for org ${organizationId} on chain ${chain.name}` ); - const signer = await initializeWalletSigner(organizationId, rpcUrl, chainId); + const signer = await initializeWalletSigner( + organizationId, + rpcUrl, + chainId + ); const provider = signer.provider; if (!provider) { diff --git a/deploy/keeperhub/prod/values.yaml b/deploy/keeperhub/prod/values.yaml index 786a81ce8..5788c76fe 100644 --- a/deploy/keeperhub/prod/values.yaml +++ b/deploy/keeperhub/prod/values.yaml @@ -150,9 +150,6 @@ env: HOSTNAME: type: kv value: "0.0.0.0" - PARA_ENVIRONMENT: - type: kv - value: "prod" WORKFLOW_TARGET_WORLD: type: kv value: "@workflow/world-postgres" @@ -223,10 +220,6 @@ env: AI_MODEL: type: kv value: "gpt-4o" - PARA_API_KEY: - type: parameterStore - name: para-api-key - parameter_name: /eks/techops-prod/keeperhub/para-api-key TURNKEY_API_PUBLIC_KEY: type: parameterStore name: turnkey-api-public-key diff --git a/deploy/keeperhub/staging/values.yaml b/deploy/keeperhub/staging/values.yaml index 1f215828d..a67989555 100644 --- a/deploy/keeperhub/staging/values.yaml +++ b/deploy/keeperhub/staging/values.yaml @@ -153,9 +153,6 @@ env: HOSTNAME: type: kv value: "0.0.0.0" - PARA_ENVIRONMENT: - type: kv - value: "beta" WORKFLOW_TARGET_WORLD: type: kv value: "@workflow/world-postgres" @@ -213,10 +210,6 @@ env: AI_MODEL: type: kv value: "gpt-4o" - PARA_API_KEY: - type: parameterStore - name: para-api-key - parameter_name: /eks/techops-staging/keeperhub/para-api-key TURNKEY_API_PUBLIC_KEY: type: parameterStore name: turnkey-api-public-key diff --git a/deploy/pr-environment/values.template.yaml b/deploy/pr-environment/values.template.yaml index e89af2aca..f407e115e 100644 --- a/deploy/pr-environment/values.template.yaml +++ b/deploy/pr-environment/values.template.yaml @@ -66,15 +66,9 @@ deployment: pnpm db:migrate echo "Seeding chains and tokens..." pnpm db:seed - echo "Seeding persistent test account..." - pnpm db:seed-test-wallet echo "Migrations and seeding completed successfully" env: <<: *shared_env - PARA_API_KEY: - type: parameterStore - name: para-api-key - parameter_name: /eks/techops-staging/keeperhub/para-api-key TURNKEY_API_PUBLIC_KEY: type: parameterStore name: turnkey-api-public-key @@ -87,16 +81,6 @@ deployment: type: parameterStore name: turnkey-organization-id parameter_name: /eks/techops-staging/keeperhub/turnkey-organization-id - PARA_ENVIRONMENT: - type: kv - value: "beta" - WALLET_ENCRYPTION_KEY: - type: parameterStore - name: wallet-encryption-key - parameter_name: /eks/techops-staging/keeperhub/wallet-encryption-key - TEST_PARA_USER_SHARE: - type: kv - value: "${TEST_PARA_USER_SHARE}" serviceAccount: create: true @@ -150,9 +134,6 @@ env: HOSTNAME: type: kv value: "0.0.0.0" - PARA_ENVIRONMENT: - type: kv - value: "beta" WORKFLOW_TARGET_WORLD: type: kv value: "@workflow/world-postgres" @@ -209,10 +190,6 @@ env: AI_MODEL: type: kv value: "gpt-4o-mini" - PARA_API_KEY: - type: parameterStore - name: para-api-key - parameter_name: /eks/techops-staging/keeperhub/para-api-key TURNKEY_API_PUBLIC_KEY: type: parameterStore name: turnkey-api-public-key diff --git a/drizzle/0069_keep_230_decom_para.sql b/drizzle/0069_keep_230_decom_para.sql new file mode 100644 index 000000000..6650965d2 --- /dev/null +++ b/drizzle/0069_keep_230_decom_para.sql @@ -0,0 +1,19 @@ +-- KEEP-230: Decommission Para wallet provider. +-- +-- Drops Para-specific columns from the wallets table now that all production +-- signing is performed via Turnkey. Inactive Para rows are removed first so +-- the surviving rows are guaranteed to have Turnkey credentials. The table is +-- renamed from `para_wallets` to `organization_wallets` to match its purpose. + +DELETE FROM para_wallets WHERE provider IS DISTINCT FROM 'turnkey'; + +ALTER TABLE para_wallets DROP COLUMN IF EXISTS provider; +ALTER TABLE para_wallets DROP COLUMN IF EXISTS para_wallet_id; +ALTER TABLE para_wallets DROP COLUMN IF EXISTS user_share; + +ALTER TABLE para_wallets RENAME TO organization_wallets; + +ALTER TABLE organization_wallets RENAME CONSTRAINT "para_wallets_user_id_users_id_fk" TO "organization_wallets_user_id_users_id_fk"; +ALTER TABLE organization_wallets RENAME CONSTRAINT "para_wallets_organization_id_organization_id_fk" TO "organization_wallets_organization_id_organization_id_fk"; + +ALTER INDEX IF EXISTS "para_wallets_org_active_unique" RENAME TO "organization_wallets_org_active_unique"; diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index ffc34a4c6..c7d1e8547 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -484,6 +484,13 @@ "when": 1777760000002, "tag": "0068_listing_version", "breakpoints": true + }, + { + "idx": 69, + "version": "7", + "when": 1777760000003, + "tag": "0069_keep_230_decom_para", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/relations.ts b/drizzle/relations.ts index d938ee268..65d85a378 100644 --- a/drizzle/relations.ts +++ b/drizzle/relations.ts @@ -1,5 +1,5 @@ import { relations } from "drizzle-orm/relations"; -import { users, sessions, workflowExecutions, workflowExecutionLogs, integrations, organization, organizationApiKeys, accounts, workflows, workflowSchedules, paraWallets, organizationTokens, apiKeys, tags, invitation, member, addressBookEntry, projects, chains, explorerConfigs, userRpcPreferences } from "./schema"; +import { users, sessions, workflowExecutions, workflowExecutionLogs, integrations, organization, organizationApiKeys, accounts, workflows, workflowSchedules, organizationWallets, organizationTokens, apiKeys, tags, invitation, member, addressBookEntry, projects, chains, explorerConfigs, userRpcPreferences } from "./schema"; export const sessionsRelations = relations(sessions, ({one}) => ({ user: one(users, { @@ -14,7 +14,7 @@ export const usersRelations = relations(users, ({many}) => ({ organizationApiKeys: many(organizationApiKeys), accounts: many(accounts), workflowExecutions: many(workflowExecutions), - paraWallets: many(paraWallets), + organizationWallets: many(organizationWallets), apiKeys: many(apiKeys), tags: many(tags), invitations: many(invitation), @@ -58,7 +58,7 @@ export const integrationsRelations = relations(integrations, ({one}) => ({ export const organizationRelations = relations(organization, ({many}) => ({ integrations: many(integrations), organizationApiKeys: many(organizationApiKeys), - paraWallets: many(paraWallets), + organizationWallets: many(organizationWallets), organizationTokens: many(organizationTokens), tags: many(tags), invitations: many(invitation), @@ -114,13 +114,13 @@ export const workflowSchedulesRelations = relations(workflowSchedules, ({one}) = }), })); -export const paraWalletsRelations = relations(paraWallets, ({one}) => ({ +export const organizationWalletsRelations = relations(organizationWallets, ({one}) => ({ user: one(users, { - fields: [paraWallets.userId], + fields: [organizationWallets.userId], references: [users.id] }), organization: one(organization, { - fields: [paraWallets.organizationId], + fields: [organizationWallets.organizationId], references: [organization.id] }), })); diff --git a/drizzle/schema.ts b/drizzle/schema.ts index a9aa5ac0a..1fb2d69a0 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -194,15 +194,12 @@ export const workflowSchedules = pgTable("workflow_schedules", { unique("workflow_schedules_workflow_id_unique").on(table.workflowId), ]); -export const paraWallets = pgTable("para_wallets", { +export const organizationWallets = pgTable("organization_wallets", { id: text().primaryKey().notNull(), userId: text("user_id").notNull(), organizationId: text("organization_id"), - provider: text().notNull(), email: text().notNull(), walletAddress: text("wallet_address").notNull(), - paraWalletId: text("para_wallet_id"), - userShare: text("user_share"), turnkeySubOrgId: text("turnkey_sub_org_id"), turnkeyWalletId: text("turnkey_wallet_id"), turnkeyPrivateKeyId: text("turnkey_private_key_id"), @@ -211,14 +208,13 @@ export const paraWallets = pgTable("para_wallets", { foreignKey({ columns: [table.userId], foreignColumns: [users.id], - name: "para_wallets_user_id_users_id_fk" + name: "organization_wallets_user_id_users_id_fk" }).onDelete("cascade"), foreignKey({ columns: [table.organizationId], foreignColumns: [organization.id], - name: "para_wallets_organization_id_organization_id_fk" + name: "organization_wallets_organization_id_organization_id_fk" }).onDelete("cascade"), - unique("para_wallets_organization_id_unique").on(table.organizationId), ]); export const supportedTokens = pgTable("supported_tokens", { diff --git a/keeperhub-executor/startup-checks.ts b/keeperhub-executor/startup-checks.ts index 339aefcff..b04ac908f 100644 --- a/keeperhub-executor/startup-checks.ts +++ b/keeperhub-executor/startup-checks.ts @@ -1,4 +1,4 @@ -import { and, eq } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import type { drizzle } from "drizzle-orm/postgres-js"; import { organizationWallets } from "../lib/db/schema"; @@ -15,12 +15,7 @@ export async function assertTurnkeyEnvForActiveWallets(db: Db): Promise { const rows = await db .select({ id: organizationWallets.id }) .from(organizationWallets) - .where( - and( - eq(organizationWallets.provider, "turnkey"), - eq(organizationWallets.isActive, true) - ) - ) + .where(eq(organizationWallets.isActive, true)) .limit(1); if (rows.length === 0) { diff --git a/lib/db/integrations.ts b/lib/db/integrations.ts index ef175ca01..f7b5532e0 100644 --- a/lib/db/integrations.ts +++ b/lib/db/integrations.ts @@ -6,7 +6,7 @@ import { truncateAddress } from "@/lib/address-utils"; import { getOrganizationWallet, organizationHasWallet, -} from "@/lib/para/wallet-helpers"; +} from "@/lib/web3/wallet-helpers"; import { findActionById, getIntegration as getPluginDefinition, @@ -346,7 +346,11 @@ export async function ensureWalletIntegration( // app/api/user/wallet/route.ts). try { await createIntegration( - buildWalletIntegrationPayload(userId, organizationId, wallet.walletAddress) + buildWalletIntegrationPayload( + userId, + organizationId, + wallet.walletAddress + ) ); } catch (err) { if (!isUniqueViolation(err)) { diff --git a/lib/db/schema-extensions.ts b/lib/db/schema-extensions.ts index 8bb3a4d7e..528b14b80 100644 --- a/lib/db/schema-extensions.ts +++ b/lib/db/schema-extensions.ts @@ -36,18 +36,14 @@ import { generateId } from "@/lib/utils/id"; /** * Organization Wallets table * - * Stores wallet information for Web3 integration. Supports multiple providers - * (Para MPC, Turnkey secure enclaves). Each organization can have one wallet - * (enforced by unique constraint on organizationId). - * - * Provider-specific columns are nullable since each row only uses one provider's fields. - * The `provider` column determines which fields are relevant. + * Stores Turnkey wallet information for Web3 integration. + * Each organization can have one active wallet (enforced by unique partial index on organizationId). * * NOTE: userId tracks who created the wallet, but the wallet belongs to the organization. * Only organization admins and owners can create/manage wallets. */ export const organizationWallets = pgTable( - "para_wallets", + "organization_wallets", { id: text("id") .primaryKey() @@ -58,13 +54,8 @@ export const organizationWallets = pgTable( organizationId: text("organization_id").references(() => organization.id, { onDelete: "cascade", }), - provider: text("provider").notNull().$type<"para" | "turnkey">(), email: text("email").notNull(), walletAddress: text("wallet_address").notNull(), - // Para-specific fields - paraWalletId: text("para_wallet_id"), - userShare: text("user_share"), // Encrypted MPC keyshare (Para only) - // Turnkey-specific fields turnkeySubOrgId: text("turnkey_sub_org_id"), turnkeyWalletId: text("turnkey_wallet_id"), turnkeyPrivateKeyId: text("turnkey_private_key_id"), @@ -72,20 +63,15 @@ export const organizationWallets = pgTable( createdAt: timestamp("created_at").notNull().defaultNow(), }, (table) => [ - uniqueIndex("para_wallets_org_active_unique") + uniqueIndex("organization_wallets_org_active_unique") .on(table.organizationId) .where(sql`${table.isActive} = true`), ] ); -// Backward compatibility alias -export const paraWallets = organizationWallets; - // Type exports export type OrganizationWallet = typeof organizationWallets.$inferSelect; export type NewOrganizationWallet = typeof organizationWallets.$inferInsert; -export type ParaWallet = OrganizationWallet; -export type NewParaWallet = NewOrganizationWallet; /** * Key Export Verification Codes table diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 792fbdafd..50f2f66eb 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -380,7 +380,7 @@ export { type WalletApprovalRequest, walletApprovalRequests, } from "./schema-agentic-wallets"; -// KeeperHub: Para Wallets, Organization API Keys, and Organization Tokens (imported from KeeperHub schema extensions) +// KeeperHub: Organization Wallets, Organization API Keys, and Organization Tokens (imported from KeeperHub schema extensions) // Note: Using relative path instead of @/ alias for drizzle-kit compatibility export { type BillingEvent, @@ -403,7 +403,6 @@ export { type NewOrganizationSubscription, type NewOrganizationToken, type NewOrganizationWallet, - type NewParaWallet, type NewPublicTag, type NewSupportedToken, type NewWorkflowPublicTag, @@ -419,10 +418,8 @@ export { organizationTokens, organizationWallets, overageBillingRecords, - type ParaWallet, type PendingTransaction, type PublicTag, - paraWallets, pendingTransactions, publicTags, type SupportedToken, @@ -772,7 +769,7 @@ export type WorkflowExecution = typeof workflowExecutions.$inferSelect; export type NewWorkflowExecution = typeof workflowExecutions.$inferInsert; export type WorkflowExecutionLog = typeof workflowExecutionLogs.$inferSelect; export type NewWorkflowExecutionLog = typeof workflowExecutionLogs.$inferInsert; -// ParaWallet types are exported from ./schema-extensions +// OrganizationWallet types are exported from ./schema-extensions export type ApiKey = typeof apiKeys.$inferSelect; export type NewApiKey = typeof apiKeys.$inferInsert; export type BetaAccessRequest = typeof betaAccessRequests.$inferSelect; diff --git a/lib/encryption.ts b/lib/encryption.ts deleted file mode 100644 index 841f0ed3e..000000000 --- a/lib/encryption.ts +++ /dev/null @@ -1,69 +0,0 @@ -import crypto from "node:crypto"; - -const ENCRYPTION_KEY_ENV = process.env.WALLET_ENCRYPTION_KEY; -const ALGORITHM = "aes-256-gcm"; - -// Lazy initialization - only validate when actually used (not at build time) -function getEncryptionKey(): string { - if (!ENCRYPTION_KEY_ENV || ENCRYPTION_KEY_ENV.length !== 64) { - throw new Error( - "WALLET_ENCRYPTION_KEY must be a 32-byte hex string (64 characters)" - ); - } - return ENCRYPTION_KEY_ENV; -} - -/** - * Encrypt sensitive userShare before storing in database - * Uses AES-256-GCM for authenticated encryption - * - * @param userShare - The plaintext userShare from Para SDK - * @returns Encrypted string in format: iv:authTag:encryptedData - */ -export function encryptUserShare(userShare: string): string { - const encryptionKey = getEncryptionKey(); - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv( - ALGORITHM, - Buffer.from(encryptionKey, "hex"), - iv - ); - - let encrypted = cipher.update(userShare, "utf8", "hex"); - encrypted += cipher.final("hex"); - - const authTag = cipher.getAuthTag(); - - // Format: iv:authTag:encryptedData - return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`; -} - -/** - * Decrypt userShare when needed for signing transactions - * - * @param encryptedData - Encrypted string from database - * @returns Decrypted userShare for Para SDK - */ -export function decryptUserShare(encryptedData: string): string { - const encryptionKey = getEncryptionKey(); - const parts = encryptedData.split(":"); - if (parts.length !== 3) { - throw new Error("Invalid encrypted data format"); - } - - const iv = Buffer.from(parts[0], "hex"); - const authTag = Buffer.from(parts[1], "hex"); - const encrypted = parts[2]; - - const decipher = crypto.createDecipheriv( - ALGORITHM, - Buffer.from(encryptionKey, "hex"), - iv - ); - decipher.setAuthTag(authTag); - - let decrypted = decipher.update(encrypted, "hex", "utf8"); - decrypted += decipher.final("utf8"); - - return decrypted; -} diff --git a/lib/metrics/METRICS_REFERENCE.md b/lib/metrics/METRICS_REFERENCE.md index 5a6f11670..49876fbfe 100644 --- a/lib/metrics/METRICS_REFERENCE.md +++ b/lib/metrics/METRICS_REFERENCE.md @@ -35,12 +35,12 @@ Understanding the user/org/wallet model helps interpret metrics correctly: | Entity | Description | Expected Relationships | |--------|-------------|------------------------| | **User** | Registered or anonymous account | Each registered user auto-gets a personal org | -| **Organization** | Multi-tenant container for workflows/credentials | Each org auto-gets a Para wallet | -| **Para Wallet** | MPC wallet for blockchain signing | 1:1 with organizations | +| **Organization** | Multi-tenant container for workflows/credentials | Each org auto-gets a Turnkey wallet | +| **Wallet** | Turnkey-backed signer for blockchain operations | 1:1 with organizations | | **Anonymous User** | Trial user without org | Can run workflows, but no chain operations | **Key metric relationships:** -- `org.total` ≈ `sum(wallet.total)` (1:1 org-to-wallet; wallets split across `para` / `turnkey` providers) +- `org.total` ≈ `wallet.total` (1:1 org-to-wallet) - `user.total` ≥ `org.total` (users can share orgs via invites) - `user.anonymous` = users without orgs (trial mode) - Web3 steps (`transfer-funds`, `write-contract`) require org + wallet @@ -183,8 +183,7 @@ Billing-aware observability layered onto the org model. Plan distribution, per-o | `apikey.total` | Total API keys | - | DB | | `chain.total` | Total blockchain networks configured | - | DB | | `chain.enabled` | Enabled blockchain networks | - | DB | -| `wallet.total` | Total active org wallets by provider | `provider` (`para`, `turnkey`) | DB | -| `para_wallet.total` | [Deprecated] Total active org wallets (all providers). Use `wallet.total` instead. | - | DB | +| `wallet.total` | Total active org wallets | - | DB | | `session.active` | Active (non-expired) sessions | - | DB | --- @@ -274,7 +273,7 @@ The following tables are queried: - `workflow_schedules` - schedule counts, enabled status, last run status - `api_keys` - API key count - `chains` - blockchain network count -- `para_wallets` - Active org wallet count (split by provider: `para`, `turnkey`) +- `organization_wallets` - Active org wallet count ### Multi-Pod Aggregation (Important) @@ -310,7 +309,7 @@ sum by (status) (keeperhub_workflow_executions_total{...}) | Workflow | `workflow_total`, `workflow_by_visibility`, `workflow_anonymous_total`, `workflow_executions_total`, `workflow_execution_errors_total`, `workflow_queue_depth`, `workflow_concurrent_count` | | Schedule | `schedule_total`, `schedule_enabled_total`, `schedule_by_last_status` | | Integration | `integration_total`, `integration_managed_total`, `integration_by_type` | -| Infrastructure | `apikey_total`, `wallet_total`, `para_wallet_total`, `chain_total`, `chain_enabled_total`, `session_active_total` | +| Infrastructure | `apikey_total`, `wallet_total`, `chain_total`, `chain_enabled_total`, `session_active_total` | **Why this happens:** Each pod queries the same PostgreSQL database and reports the same gauge value. With 2 pods reporting 21 users each, `sum()` returns 42 while `max()` correctly returns 21. @@ -472,7 +471,6 @@ Prometheus metrics are prefixed with `keeperhub_` and use snake_case: | `chain.total` | `keeperhub_chain_total` | gauge | | `chain.enabled` | `keeperhub_chain_enabled_total` | gauge | | `wallet.total` | `keeperhub_wallet_total` | gauge | -| `para_wallet.total` | `keeperhub_para_wallet_total` | gauge (deprecated) | | `session.active` | `keeperhub_session_active_total` | gauge | | `api.webhook.latency_ms` | `keeperhub_api_webhook_latency_ms` | histogram | | `api.status.latency_ms` | `keeperhub_api_status_latency_ms` | histogram | diff --git a/lib/metrics/collectors/prometheus.ts b/lib/metrics/collectors/prometheus.ts index 927478611..688965f3f 100644 --- a/lib/metrics/collectors/prometheus.ts +++ b/lib/metrics/collectors/prometheus.ts @@ -433,22 +433,11 @@ const chainEnabled = getOrCreateGauge( [] ); -/** - * @deprecated Counts all active org wallets (Para + Turnkey) and is retained - * for backward compatibility. Use `keeperhub_wallet_total{provider}` instead. - */ -const paraWalletTotal = getOrCreateGauge( - dbRegistry, - "keeperhub_para_wallet_total", - "[Deprecated] Total active org wallets (all providers). Use keeperhub_wallet_total{provider} instead.", - [] -); - -const walletTotalByProvider = getOrCreateGauge( +const walletTotal = getOrCreateGauge( dbRegistry, "keeperhub_wallet_total", - "Total active org wallets by provider", - ["provider"] + "Total active org wallets", + [] ); const sessionActive = getOrCreateGauge( @@ -1370,15 +1359,7 @@ export async function updateDbMetrics(): Promise { apiKeyTotal.set(infraStats.apiKeysTotal); chainTotal.set(infraStats.chainsTotal); chainEnabled.set(infraStats.chainsEnabled); - paraWalletTotal.set(infraStats.paraWalletsTotal); - walletTotalByProvider.set( - { provider: "para" }, - infraStats.walletsByProvider.para - ); - walletTotalByProvider.set( - { provider: "turnkey" }, - infraStats.walletsByProvider.turnkey - ); + walletTotal.set(infraStats.walletsTotal); sessionActive.set(infraStats.sessionsActive); updateHubVoteMetrics(voteStats); diff --git a/lib/metrics/db-metrics.ts b/lib/metrics/db-metrics.ts index 69b8f2178..8f2aee144 100644 --- a/lib/metrics/db-metrics.ts +++ b/lib/metrics/db-metrics.ts @@ -26,7 +26,7 @@ import { member, organization, organizationSubscriptions, - paraWallets, + organizationWallets, sessions, users, workflowExecutionLogs, @@ -714,16 +714,7 @@ export type InfraStats = { apiKeysTotal: number; chainsTotal: number; chainsEnabled: number; - /** - * @deprecated Counts all active org wallets regardless of provider. - * Kept for backward compatibility with the `keeperhub_para_wallet_total` - * gauge. Use `walletsByProvider` instead. - */ - paraWalletsTotal: number; - walletsByProvider: { - para: number; - turnkey: number; - }; + walletsTotal: number; sessionsActive: number; }; @@ -741,7 +732,6 @@ export async function getInfraStatsFromDb(): Promise { chainsResult, chainsEnabledResult, walletsResult, - walletsByProviderResult, sessionsResult, ] = await Promise.all([ db.select({ count: count() }).from(apiKeys), @@ -752,32 +742,19 @@ export async function getInfraStatsFromDb(): Promise { .where(eq(chains.isEnabled, true)), db .select({ count: count() }) - .from(paraWallets) - .where(eq(paraWallets.isActive, true)), - db - .select({ provider: paraWallets.provider, count: count() }) - .from(paraWallets) - .where(eq(paraWallets.isActive, true)) - .groupBy(paraWallets.provider), + .from(organizationWallets) + .where(eq(organizationWallets.isActive, true)), db .select({ count: count() }) .from(sessions) .where(gte(sessions.expiresAt, now)), ]); - const walletsByProvider = { para: 0, turnkey: 0 }; - for (const row of walletsByProviderResult) { - if (row.provider === "para" || row.provider === "turnkey") { - walletsByProvider[row.provider] = Number(row.count) || 0; - } - } - return { apiKeysTotal: Number(apiKeysResult[0]?.count) || 0, chainsTotal: Number(chainsResult[0]?.count) || 0, chainsEnabled: Number(chainsEnabledResult[0]?.count) || 0, - paraWalletsTotal: Number(walletsResult[0]?.count) || 0, - walletsByProvider, + walletsTotal: Number(walletsResult[0]?.count) || 0, sessionsActive: Number(sessionsResult[0]?.count) || 0, }; } catch (error) { @@ -786,8 +763,7 @@ export async function getInfraStatsFromDb(): Promise { apiKeysTotal: 0, chainsTotal: 0, chainsEnabled: 0, - paraWalletsTotal: 0, - walletsByProvider: { para: 0, turnkey: 0 }, + walletsTotal: 0, sessionsActive: 0, }; } diff --git a/lib/para/viem-account-adapter.ts b/lib/para/viem-account-adapter.ts deleted file mode 100644 index f214f9eff..000000000 --- a/lib/para/viem-account-adapter.ts +++ /dev/null @@ -1,167 +0,0 @@ -import "server-only"; -import { Environment, Para as ParaServer } from "@getpara/server-sdk"; -import type { - Address, - AuthorizationRequest, - Hex, - LocalAccount, - SignableMessage, - SignedAuthorization, -} from "viem"; -import { - hashAuthorization, - hashMessage, - hashTypedData, - serializeTransaction, -} from "viem/utils"; -import { decryptUserShare } from "@/lib/encryption"; -import { getOrganizationWallet } from "@/lib/para/wallet-helpers"; - -type ParaWalletRecord = { - userId: string; - paraWalletId: string | null; - walletAddress: string; - userShare: string | null; -}; - -function hexToBase64(hex: string): string { - const clean = hex.startsWith("0x") ? hex.slice(2) : hex; - return Buffer.from(clean, "hex").toString("base64"); -} - -function parseSignature(sigHex: string): { r: Hex; s: Hex; v: bigint } { - const clean = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex; - const r = `0x${clean.slice(0, 64)}` as Hex; - const s = `0x${clean.slice(64, 128)}` as Hex; - const vByte = Number.parseInt(clean.slice(128, 130), 16); - const v = BigInt(vByte < 27 ? vByte + 27 : vByte); - return { r, s, v }; -} - -function initializeParaClient(): ParaServer { - const apiKey = process.env.PARA_API_KEY; - if (!apiKey) { - throw new Error("PARA_API_KEY not configured"); - } - const env = process.env.PARA_ENVIRONMENT || "beta"; - const disableWebSockets = process.env.PARA_DISABLE_WEBSOCKETS === "true"; - return new ParaServer( - env === "prod" ? Environment.PROD : Environment.BETA, - apiKey, - { disableWebSockets } - ); -} - -/** - * Creates a viem LocalAccount backed by Para's MPC signing. - * - * The returned account delegates signMessage/signTypedData/signTransaction - * to Para's server-side MPC protocol via user shares. This allows it to - * be used as the `owner` for permissionless.js smart account clients. - * - * Para's signMessage signs raw bytes directly -- the EIP-191/712 prefixes - * are added by viem's hashMessage/hashTypedData at the adapter layer. - * This means account.sign({ hash }) works for EIP-7702 authorization - * signing via hashAuthorization() + sign(). - */ -export async function createParaViemAccount( - organizationId: string -): Promise<{ account: LocalAccount; walletRecord: ParaWalletRecord }> { - const walletRecord = await getOrganizationWallet(organizationId); - const paraClient = initializeParaClient(); - - if (!(walletRecord.userShare && walletRecord.paraWalletId)) { - throw new Error( - "Wallet missing Para credentials (userShare or paraWalletId)" - ); - } - - const decryptedShare = decryptUserShare(walletRecord.userShare); - await paraClient.setUserShare(decryptedShare); - - const walletId = walletRecord.paraWalletId; - const address = walletRecord.walletAddress as Address; - - async function signRawHash(hash: Hex): Promise { - const res = await paraClient.signMessage({ - walletId, - messageBase64: hexToBase64(hash), - }); - if (!("signature" in res)) { - throw new Error("Para signing was denied"); - } - // Para returns v as yParity (0/1) but on-chain ECDSA expects v=27/28 - const sig = res.signature; - const vByte = Number.parseInt(sig.slice(-2), 16); - if (vByte < 27) { - const normalizedV = (vByte + 27).toString(16).padStart(2, "0"); - return `0x${sig.slice(0, -2)}${normalizedV}` as Hex; - } - return `0x${sig}` as Hex; - } - - const account: LocalAccount = { - address, - // publicKey is not recoverable from address alone; permissionless.js - // only uses the address field from the owner account - publicKey: "0x" as Hex, - source: "para" as string, - type: "local", - - async sign({ hash }: { hash: Hex }): Promise { - return await signRawHash(hash); - }, - - async signMessage({ message }: { message: SignableMessage }): Promise { - const hash = hashMessage(message); - return await signRawHash(hash); - }, - - async signTransaction(transaction, _options?): Promise { - const serialized = serializeTransaction(transaction); - const res = await paraClient.signMessage({ - walletId, - messageBase64: hexToBase64(serialized), - }); - if (!("signature" in res)) { - throw new Error("Para transaction signing was denied"); - } - const { r, s, v } = parseSignature(res.signature); - const yParity = v === BigInt(28) ? 1 : 0; - return serializeTransaction(transaction, { - r, - s, - yParity, - }); - }, - - // biome-ignore lint/suspicious/noExplicitAny: viem TypedDataDefinition generic is complex - async signTypedData(parameters: any): Promise { - const hash = hashTypedData(parameters); - return await signRawHash(hash); - }, - - async signAuthorization( - authorization: AuthorizationRequest - ): Promise { - const hash = hashAuthorization(authorization); - const sigHex = await signRawHash(hash); - const { r, s, v } = parseSignature(sigHex); - const yParity = v === BigInt(28) ? 1 : 0; - const contractAddress = - "address" in authorization - ? authorization.address - : authorization.contractAddress; - return { - address: contractAddress, - chainId: authorization.chainId ?? 0, - nonce: authorization.nonce ?? 0, - r, - s, - yParity, - } as SignedAuthorization; - }, - }; - - return { account, walletRecord }; -} diff --git a/lib/web3/sponsored-client.ts b/lib/web3/sponsored-client.ts index e4e581fe8..345640041 100644 --- a/lib/web3/sponsored-client.ts +++ b/lib/web3/sponsored-client.ts @@ -9,13 +9,13 @@ import { entryPoint08Address } from "viem/account-abstraction"; import { db } from "@/lib/db"; import { chains } from "@/lib/db/schema"; import { ErrorCategory, logSystemError } from "@/lib/logging"; -import { createParaViemAccount } from "@/lib/para/viem-account-adapter"; import { recordDelegationIfNeeded } from "@/lib/web3/eip7702-delegation"; import { getPimlicoUrl, isSponsorshipSupported, } from "@/lib/web3/pimlico-config"; import { clampSponsoredFees } from "@/lib/web3/sponsored-fee-clamp"; +import { createTurnkeyViemAccount } from "@/lib/web3/turnkey-viem-account"; const LOG_PREFIX = "[Sponsorship]"; @@ -33,7 +33,7 @@ type SponsoredClientResult = { * Creates a sponsored smart account client for an organization. * * This: - * 1. Creates a viem account backed by Para MPC signing + * 1. Creates a viem account backed by Turnkey signing * 2. Creates a Pimlico-sponsored smart account client with EIP-7702 support * * Returns the smart account client plus the account/publicClient needed for @@ -53,7 +53,7 @@ export async function createSponsoredClient( try { const { account, walletRecord } = - await createParaViemAccount(organizationId); + await createTurnkeyViemAccount(organizationId); const walletAddress = walletRecord.walletAddress as Address; const chainRecord = await db.query.chains.findFirst({ where: eq(chains.chainId, chainId), diff --git a/lib/web3/transaction-manager.ts b/lib/web3/transaction-manager.ts index d8cd0874c..d2bdf29c8 100644 --- a/lib/web3/transaction-manager.ts +++ b/lib/web3/transaction-manager.ts @@ -22,7 +22,6 @@ import { db } from "@/lib/db"; import { explorerConfigs } from "@/lib/db/schema"; import { getTransactionUrl } from "@/lib/explorer"; import { ErrorCategory, logUserError } from "@/lib/logging"; -import { initializeWalletSigner } from "@/lib/para/wallet-helpers"; import { getRpcProviderFromUrls } from "@/lib/rpc/provider-factory"; import type { RpcProviderManager } from "@/lib/rpc/providers"; import { isNonRetryableError } from "@/lib/rpc/providers/error-classification"; @@ -31,6 +30,7 @@ import { rpcMetricsCtx, withRpcMetrics, } from "@/lib/rpc/providers/with-rpc-metrics"; +import { initializeWalletSigner } from "@/lib/web3/wallet-helpers"; import { getGasStrategy } from "./gas-strategy"; import { getNonceManager, type NonceSession } from "./nonce-manager"; diff --git a/lib/web3/turnkey-viem-account.ts b/lib/web3/turnkey-viem-account.ts new file mode 100644 index 000000000..4dc1aba34 --- /dev/null +++ b/lib/web3/turnkey-viem-account.ts @@ -0,0 +1,167 @@ +import "server-only"; +import type { + Address, + AuthorizationRequest, + Hex, + LocalAccount, + SignableMessage, + SignedAuthorization, +} from "viem"; +import { + hashAuthorization, + hashMessage, + hashTypedData, + serializeTransaction, +} from "viem/utils"; +import { toChecksumAddress } from "@/lib/address-utils"; +import type { OrganizationWallet } from "@/lib/db/schema"; +import { getTurnkeySignerConfig } from "@/lib/turnkey/turnkey-client"; +import { getOrganizationWallet } from "@/lib/web3/wallet-helpers"; + +function parseSignature( + r: string, + s: string, + v: string +): { + r: Hex; + s: Hex; + v: bigint; +} { + const rHex = (r.startsWith("0x") ? r : `0x${r}`) as Hex; + const sHex = (s.startsWith("0x") ? s : `0x${s}`) as Hex; + const vBigInt = BigInt(`0x${v}`); + const normalizedV = vBigInt < BigInt(27) ? vBigInt + BigInt(27) : vBigInt; + return { r: rHex, s: sHex, v: normalizedV }; +} + +async function signHashWithTurnkey( + client: ReturnType["client"], + signWith: string, + organizationId: string, + hash: Hex +): Promise<{ r: Hex; s: Hex; v: bigint }> { + const cleanHash = hash.startsWith("0x") ? hash.slice(2) : hash; + const result = await client.signRawPayload({ + organizationId, + signWith, + payload: cleanHash, + encoding: "PAYLOAD_ENCODING_HEXADECIMAL", + hashFunction: "HASH_FUNCTION_NO_OP", + }); + + return parseSignature(result.r, result.s, result.v); +} + +function combineSignature(sig: { r: Hex; s: Hex; v: bigint }): Hex { + const r = sig.r.startsWith("0x") ? sig.r.slice(2) : sig.r; + const s = sig.s.startsWith("0x") ? sig.s.slice(2) : sig.s; + const v = sig.v.toString(16).padStart(2, "0"); + return `0x${r}${s}${v}` as Hex; +} + +/** + * Creates a viem LocalAccount backed by Turnkey signing. + * + * Used as the `owner` for permissionless.js smart account clients to enable + * EIP-7702 authorization signing and ERC-4337 user operation signing. + */ +export async function createTurnkeyViemAccount( + organizationId: string +): Promise<{ account: LocalAccount; walletRecord: OrganizationWallet }> { + const walletRecord = await getOrganizationWallet(organizationId); + + if (!walletRecord.turnkeySubOrgId) { + throw new Error("Wallet missing Turnkey sub-organization ID"); + } + + const config = getTurnkeySignerConfig( + walletRecord.turnkeySubOrgId, + toChecksumAddress(walletRecord.walletAddress) + ); + + const address = walletRecord.walletAddress as Address; + + const account: LocalAccount = { + address, + publicKey: "0x" as Hex, + source: "turnkey" as string, + type: "local", + + async sign({ hash }: { hash: Hex }): Promise { + const sig = await signHashWithTurnkey( + config.client, + config.signWith, + config.organizationId, + hash + ); + return combineSignature(sig); + }, + + async signMessage({ message }: { message: SignableMessage }): Promise { + const hash = hashMessage(message); + const sig = await signHashWithTurnkey( + config.client, + config.signWith, + config.organizationId, + hash + ); + return combineSignature(sig); + }, + + async signTransaction(transaction, _options?): Promise { + const serialized = serializeTransaction(transaction); + const cleanPayload = serialized.startsWith("0x") + ? serialized.slice(2) + : serialized; + const result = await config.client.signRawPayload({ + organizationId: config.organizationId, + signWith: config.signWith, + payload: cleanPayload, + encoding: "PAYLOAD_ENCODING_HEXADECIMAL", + hashFunction: "HASH_FUNCTION_KECCAK256", + }); + const { r, s, v } = parseSignature(result.r, result.s, result.v); + const yParity = v === BigInt(28) ? 1 : 0; + return serializeTransaction(transaction, { r, s, yParity }); + }, + + // biome-ignore lint/suspicious/noExplicitAny: viem TypedDataDefinition generic is complex + async signTypedData(parameters: any): Promise { + const hash = hashTypedData(parameters); + const sig = await signHashWithTurnkey( + config.client, + config.signWith, + config.organizationId, + hash + ); + return combineSignature(sig); + }, + + async signAuthorization( + authorization: AuthorizationRequest + ): Promise { + const hash = hashAuthorization(authorization); + const { r, s, v } = await signHashWithTurnkey( + config.client, + config.signWith, + config.organizationId, + hash + ); + const yParity = v === BigInt(28) ? 1 : 0; + const contractAddress = + "address" in authorization + ? authorization.address + : authorization.contractAddress; + return { + address: contractAddress, + chainId: authorization.chainId ?? 0, + nonce: authorization.nonce ?? 0, + r, + s, + yParity, + } as SignedAuthorization; + }, + }; + + return { account, walletRecord }; +} diff --git a/lib/para/wallet-helpers.ts b/lib/web3/wallet-helpers.ts similarity index 59% rename from lib/para/wallet-helpers.ts rename to lib/web3/wallet-helpers.ts index 672755ee7..41662b525 100644 --- a/lib/para/wallet-helpers.ts +++ b/lib/web3/wallet-helpers.ts @@ -10,8 +10,6 @@ import { getTurnkeySignerConfig } from "@/lib/turnkey/turnkey-client"; /** * Get organization's active wallet from database. - * During Para → Turnkey migration an org may temporarily hold both wallets; - * only the one flagged `isActive = true` is used by workflows and integrations. */ export async function getOrganizationWallet( organizationId: string @@ -34,33 +32,6 @@ export async function getOrganizationWallet( return wallet[0]; } -/** - * @deprecated Use getOrganizationWallet instead - */ -export async function getUserWallet(userId: string) { - const wallet = await db - .select() - .from(organizationWallets) - .where( - and( - eq(organizationWallets.userId, userId), - eq(organizationWallets.isActive, true) - ) - ) - .limit(1); - - if (wallet.length === 0) { - throw new Error("No wallet found for user"); - } - - return wallet[0]; -} - -/** - * Initialize an ethers-compatible signer for the organization's active wallet. - * Turnkey is the only supported signer; legacy Para wallets throw so the - * org is forced to provision a Turnkey wallet via the create flow. - */ export async function initializeWalletSigner( organizationId: string, rpcUrl: string, @@ -70,11 +41,6 @@ export async function initializeWalletSigner( const rpcManager = await getRpcProviderFromUrls(rpcUrl, undefined, chainId); const provider = rpcManager.getProvider(); - if (wallet.provider !== "turnkey") { - throw new Error( - "Para signing is no longer supported. Recreate the wallet via the wallet UI to use Turnkey." - ); - } return initializeTurnkeySigner(wallet, provider); } @@ -100,9 +66,6 @@ function initializeTurnkeySigner( return signer.connect(provider); } -/** - * Get organization's wallet address - */ export async function getOrganizationWalletAddress( organizationId: string ): Promise { @@ -110,9 +73,6 @@ export async function getOrganizationWalletAddress( return wallet.walletAddress; } -/** - * Check if organization has a wallet - */ export async function organizationHasWallet( organizationId: string ): Promise { @@ -129,29 +89,3 @@ export async function organizationHasWallet( return wallet.length > 0; } - -/** - * @deprecated Use getOrganizationWalletAddress instead - */ -export async function getUserWalletAddress(userId: string): Promise { - const wallet = await getUserWallet(userId); - return wallet.walletAddress; -} - -/** - * @deprecated Use organizationHasWallet instead - */ -export async function userHasWallet(userId: string): Promise { - const wallet = await db - .select() - .from(organizationWallets) - .where( - and( - eq(organizationWallets.userId, userId), - eq(organizationWallets.isActive, true) - ) - ) - .limit(1); - - return wallet.length > 0; -} diff --git a/package.json b/package.json index 2e4743171..62eac22c0 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,7 @@ "db:studio": "drizzle-kit studio", "db:seed": "tsx scripts/seed/seed-chains.ts && tsx scripts/seed/seed-tokens.ts", "db:seed-chains": "tsx scripts/seed/seed-chains.ts", - "db:seed-test-wallet": "tsx scripts/seed/seed-test-wallet.ts", "db:seed-x402": "tsx scripts/seed/seed-x402-test.ts", - "db:fund-test-wallet": "tsx scripts/miscellaneous/fund-test-wallet.ts", "db:seed-tokens": "tsx scripts/seed/seed-tokens.ts", "db:setup": "pnpm db:migrate && pnpm db:setup-workflow && pnpm db:seed", "db:setup-workflow": "workflow-postgres-setup", @@ -57,8 +55,6 @@ "@ai-sdk/provider": "^2.0.1", "@aws-sdk/client-sqs": "^3.1024.0", "@coinbase/x402": "^2.1.0", - "@getpara/ethers-v6-integration": "2.20.0", - "@getpara/server-sdk": "2.20.0", "@kubernetes/client-node": "^1.4.0", "@linear/sdk": "^63.4.0", "@mendable/firecrawl-js": "^4.18.1", diff --git a/plugins/web3/index.ts b/plugins/web3/index.ts index 8c2332b18..3d782989f 100644 --- a/plugins/web3/index.ts +++ b/plugins/web3/index.ts @@ -5,11 +5,11 @@ import { Web3Icon } from "./icon"; const web3Plugin: IntegrationPlugin = { type: "web3", label: "Web3", - description: "Interact with blockchain networks using your Para wallet", + description: "Interact with blockchain networks using your KeeperHub wallet", icon: Web3Icon, - // Web3 uses Para wallet - one wallet per user + // One wallet per organization singleConnection: true, // Read-only actions (check balance, read contract) don't require a wallet diff --git a/plugins/web3/steps/approve-token-core.ts b/plugins/web3/steps/approve-token-core.ts index 12283f637..ce70e356b 100644 --- a/plugins/web3/steps/approve-token-core.ts +++ b/plugins/web3/steps/approve-token-core.ts @@ -17,7 +17,7 @@ import { ErrorCategory, logUserError } from "@/lib/logging"; import { getOrganizationWalletAddress, initializeWalletSigner, -} from "@/lib/para/wallet-helpers"; +} from "@/lib/web3/wallet-helpers"; import { getChainIdFromNetwork } from "@/lib/rpc/network-utils"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { getErrorMessage } from "@/lib/utils"; @@ -302,7 +302,7 @@ export async function approveTokenCore( const adapter = getChainAdapter(chainId); return withNonceSession(txContext, walletAddress, async (session) => { - // Initialize Para signer + // Initialize wallet signer let signer: Awaited>; try { signer = await initializeWalletSigner(organizationId, rpcUrl, chainId); diff --git a/plugins/web3/steps/transfer-funds-core.ts b/plugins/web3/steps/transfer-funds-core.ts index d8316e33c..f87ba636b 100644 --- a/plugins/web3/steps/transfer-funds-core.ts +++ b/plugins/web3/steps/transfer-funds-core.ts @@ -16,7 +16,7 @@ import { ErrorCategory, logUserError } from "@/lib/logging"; import { getOrganizationWalletAddress, initializeWalletSigner, -} from "@/lib/para/wallet-helpers"; +} from "@/lib/web3/wallet-helpers"; import { getChainIdFromNetwork } from "@/lib/rpc/network-utils"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { getErrorMessage } from "@/lib/utils"; diff --git a/plugins/web3/steps/transfer-token-core.ts b/plugins/web3/steps/transfer-token-core.ts index 53262b94d..24961fdff 100644 --- a/plugins/web3/steps/transfer-token-core.ts +++ b/plugins/web3/steps/transfer-token-core.ts @@ -21,7 +21,7 @@ import { ErrorCategory, logUserError } from "@/lib/logging"; import { getOrganizationWalletAddress, initializeWalletSigner, -} from "@/lib/para/wallet-helpers"; +} from "@/lib/web3/wallet-helpers"; import { getChainIdFromNetwork } from "@/lib/rpc/network-utils"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { getErrorMessage } from "@/lib/utils"; @@ -401,7 +401,7 @@ export async function transferTokenCore( const adapter = getChainAdapter(chainId); return withNonceSession(txContext, walletAddress, async (session) => { - // Initialize Para signer + // Initialize wallet signer let signer: Awaited>; let signerAddress: string; try { diff --git a/plugins/web3/steps/write-contract-core.ts b/plugins/web3/steps/write-contract-core.ts index a65c92b1f..fedc1cd29 100644 --- a/plugins/web3/steps/write-contract-core.ts +++ b/plugins/web3/steps/write-contract-core.ts @@ -18,7 +18,7 @@ import { ErrorCategory, logUserError } from "@/lib/logging"; import { getOrganizationWalletAddress, initializeWalletSigner, -} from "@/lib/para/wallet-helpers"; +} from "@/lib/web3/wallet-helpers"; import { getChainIdFromNetwork } from "@/lib/rpc/network-utils"; import { getRpcProvider } from "@/lib/rpc/provider-factory"; import { findAbiFunction } from "@/lib/abi/utils"; @@ -350,7 +350,7 @@ export async function writeContractCore( const adapter = getChainAdapter(chainId); return withNonceSession(txContext, walletAddress, async (session) => { - // Initialize Para signer + // Initialize wallet signer let signer: Awaited>; try { signer = await initializeWalletSigner(organizationId, rpcUrl, chainId); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0c7c1d30..5c9b52104 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,12 +43,6 @@ importers: '@coinbase/x402': specifier: ^2.1.0 version: 2.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) - '@getpara/ethers-v6-integration': - specifier: 2.20.0 - version: 2.20.0(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - '@getpara/server-sdk': - specifier: 2.20.0 - version: 2.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(viem@2.47.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6)) '@kubernetes/client-node': specifier: ^1.4.0 version: 1.4.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) @@ -129,7 +123,7 @@ importers: version: 5.0.169(zod@4.3.6) better-auth: specifier: ^1.5.6 - version: 1.5.6(deahontvrs7v3pbjeoeq75dmbm) + version: 1.5.6(6d6bc65b4e207ce0ccf1adc5fe53a0f6) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -667,24 +661,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.4.10': resolution: {integrity: sha512-7MH1CMW5uuxQ/s7FLST63qF8B3Hgu2HRdZ7tA1X1+mk+St4JOuIrqdhIBnnyqeyWJNI+Bww7Es5QZ0wIc1Cmkw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.10': resolution: {integrity: sha512-kDTi3pI6PBN6CiczsWYOyP2zk0IJI08EWEQyDMQWW221rPaaEz6FvjLhnU07KMzLv8q3qSuoB93ua6inSQ55Tw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.4.10': resolution: {integrity: sha512-tZLvEEi2u9Xu1zAqRjTcpIDGVtldigVvzug2fTuPG0ME/g8/mXpRPcNgLB22bGn6FvLJpHHnqLnwliOu8xjYrg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.4.10': resolution: {integrity: sha512-umwQU6qPzH+ISTf/eHyJ/QoQnJs3V9Vpjz2OjZXe9MVBZ7prgGafMy7yYeRGnlmDAn87AKTF3Q6weLoMGpeqdQ==} @@ -731,24 +729,9 @@ packages: cpu: [x64] os: [win32] - '@celo/base@7.0.4': - resolution: {integrity: sha512-LUWVdqchXVKlp9h4Kh190wKE6DDy+zAREfVbKBIH/AWvrGv3EWQSUSDj3C8vrZjJ5wiCGt/ws+5+39Iao/W02Q==} - - '@celo/utils@8.0.3': - resolution: {integrity: sha512-eHXSqRGWzXLGnfqq4eq37JAUnalqX5EIhlXyqSmxtIxc0Shkzlq7hrMpxI4diUS/T2zCcs9OQtr2ihjGxzqtqA==} - '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} - '@chainsafe/as-sha256@0.3.1': - resolution: {integrity: sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==} - - '@chainsafe/persistent-merkle-tree@0.4.2': - resolution: {integrity: sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==} - - '@chainsafe/ssz@0.9.4': - resolution: {integrity: sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==} - '@chevrotain/cst-dts-gen@10.5.0': resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} @@ -773,9 +756,6 @@ packages: '@coinbase/x402@2.1.0': resolution: {integrity: sha512-aKeM+cz//+FjzPVu/zgz7830x0KLtKarwCyxoeC71QgCn+Xcf0NhFpn3Qyw0H496y5YOuR/IQ67gP8DZ/hXFqQ==} - '@cosmjs/encoding@0.32.4': - resolution: {integrity: sha512-tjvaEy6ZGxJchiizzTn7HVRiyTg1i4CObRRaTRPknm5EalE13SV+TCHq38gIDfyUeden4fCuaBVEdBR5+ti7Hw==} - '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} @@ -1446,82 +1426,6 @@ packages: cpu: [x64] os: [win32] - '@ethereumjs/rlp@4.0.1': - resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} - engines: {node: '>=14'} - hasBin: true - - '@ethereumjs/rlp@5.0.2': - resolution: {integrity: sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==} - engines: {node: '>=18'} - hasBin: true - - '@ethereumjs/util@8.0.5': - resolution: {integrity: sha512-259rXKK3b3D8HRVdRmlOEi6QFvwxdt304hhrEAmpZhsj7ufXEOTIc9JRZPMnXatKjECokdLNBcDOFBeBSzAIaw==} - engines: {node: '>=14'} - - '@ethereumjs/util@8.1.0': - resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} - engines: {node: '>=14'} - - '@ethereumjs/util@9.1.0': - resolution: {integrity: sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==} - engines: {node: '>=18'} - - '@ethersproject/abi@5.8.0': - resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} - - '@ethersproject/abstract-provider@5.8.0': - resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} - - '@ethersproject/abstract-signer@5.8.0': - resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} - - '@ethersproject/address@5.8.0': - resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} - - '@ethersproject/base64@5.8.0': - resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} - - '@ethersproject/bignumber@5.8.0': - resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} - - '@ethersproject/bytes@5.8.0': - resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} - - '@ethersproject/constants@5.8.0': - resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} - - '@ethersproject/hash@5.8.0': - resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} - - '@ethersproject/keccak256@5.8.0': - resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} - - '@ethersproject/logger@5.8.0': - resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} - - '@ethersproject/networks@5.8.0': - resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} - - '@ethersproject/properties@5.8.0': - resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} - - '@ethersproject/rlp@5.8.0': - resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} - - '@ethersproject/signing-key@5.8.0': - resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} - - '@ethersproject/strings@5.8.0': - resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} - - '@ethersproject/transactions@5.8.0': - resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} - - '@ethersproject/web@5.8.0': - resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - '@exodus/bytes@1.15.0': resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1551,28 +1455,6 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@getpara/core-sdk@2.20.0': - resolution: {integrity: sha512-Od7oxyWxuvlgWPIrfKTHLN4R+sVcLKXloz5+Fy3XXSnEVkJi/kytxJoj4cZhm2NG04DaoSPvqfX00inkFLuFZQ==} - - '@getpara/ethers-v6-integration@2.20.0': - resolution: {integrity: sha512-PkWwt06659UUlNi0MbAW6d3hmdha+YoclZRTM55oU/xWtnE1svIVMHTa5WjBBMLbxpYUaNBJmIZhpJwulLDQlg==} - peerDependencies: - ethers: ^6.13.5 - - '@getpara/server-sdk@2.20.0': - resolution: {integrity: sha512-BgmEjTvqmfaQGAw7u7C+UkbE0WX08r2tCvMHTH5rhsciBNi8sy4UA/33hSTXh2EIvdxsPMBQsISEEtY1nFpI+g==} - - '@getpara/shared@1.14.0': - resolution: {integrity: sha512-SpN6k5MBvr54ExlLqN0cUN/IJzNmKqQYr0mNeiDIQhNIut0HrKySLlZJHCX3OqpfZ2fYN0mKYyamq0m+vMzMPw==} - - '@getpara/user-management-client@2.20.0': - resolution: {integrity: sha512-t8xq/m6LcXe6hKxdGzPzayXko4ngmqI8Ehh8LTxlx7AcIVfhUy3nsbyTmwDmVBZv39OgrXfc7beKaB0OL1neqg==} - - '@getpara/viem-v2-integration@2.20.0': - resolution: {integrity: sha512-1TX1jlby9pAz5vU9s/hafz5Ant3iBze3KDTtfJZ45zK85C1g60zebFhu29yFO/pbZO4blQaCbMYfCzfwBB7qGg==} - peerDependencies: - viem: ^2.39.0 - '@graphile/logger@0.2.0': resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==} @@ -1637,89 +1519,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2009,42 +1907,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -2128,24 +2033,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.2.2': resolution: {integrity: sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.2.2': resolution: {integrity: sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.2.2': resolution: {integrity: sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.2.2': resolution: {integrity: sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==} @@ -2159,10 +2068,6 @@ packages: cpu: [x64] os: [win32] - '@noble/ciphers@1.1.3': - resolution: {integrity: sha512-Ygv6WnWJHLLiW4fnNDC1z+i13bud+enXOFRBlpxI+NJliPWx5wdR+oWlTjLuBPTqjUjtHXtjkU6w3kuuH6upZA==} - engines: {node: ^14.21.3 || >=16} - '@noble/ciphers@1.3.0': resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} @@ -2174,9 +2079,6 @@ packages: '@noble/curves@1.2.0': resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.3.0': - resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==} - '@noble/curves@1.4.2': resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} @@ -2196,17 +2098,10 @@ packages: resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.2.0': - resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} - '@noble/hashes@1.3.2': resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} engines: {node: '>= 16'} - '@noble/hashes@1.3.3': - resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} - engines: {node: '>= 16'} - '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -2223,9 +2118,6 @@ packages: resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} - '@noble/secp256k1@1.7.1': - resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2502,41 +2394,49 @@ packages: resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.19.1': resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.19.1': resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.19.1': resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.19.1': resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==} @@ -3454,131 +3354,157 @@ packages: resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.60.1': resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.0': resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.0': resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.0': resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.0': resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.0': resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.0': resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.0': resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.0': resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.0': resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.0': resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.0': resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.0': resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.0': resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} @@ -3640,27 +3566,12 @@ packages: cpu: [x64] os: [win32] - '@scure/base@1.1.9': - resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} - '@scure/base@1.2.6': resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - '@scure/bip32@1.1.5': - resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} - - '@scure/bip32@1.4.0': - resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} - '@scure/bip32@1.7.0': resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - '@scure/bip39@1.1.1': - resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} - - '@scure/bip39@1.3.0': - resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} - '@scure/bip39@1.6.0': resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} @@ -4459,24 +4370,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.3': resolution: {integrity: sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.3': resolution: {integrity: sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.3': resolution: {integrity: sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.3': resolution: {integrity: sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==} @@ -4559,24 +4474,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.2': resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} @@ -4692,9 +4611,6 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@types/bn.js@5.2.0': - resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -4779,9 +4695,6 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@18.19.130': - resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} @@ -5472,10 +5385,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - base64url@3.0.1: - resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} - engines: {node: '>=6.0.0'} - baseline-browser-mapping@2.10.10: resolution: {integrity: sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==} engines: {node: '>=6.0.0'} @@ -5486,9 +5395,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - bech32@1.1.4: - resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - better-auth@1.5.6: resolution: {integrity: sha512-QSpJTqaT1XVfWRQe/fm3PgeuwOIlz1nWX/Dx7nsHStJ382bLzmDbQk2u7IT0IJ6wS5SRxfqEE1Ev9TXontgyAQ==} peerDependencies: @@ -5562,9 +5468,6 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - bin-version-check@5.1.0: resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} engines: {node: '>=12'} @@ -5611,9 +5514,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5714,10 +5614,6 @@ packages: caniuse-lite@1.0.30001787: resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} - case@1.6.3: - resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} - engines: {node: '>= 0.8.0'} - cbor-extract@2.2.2: resolution: {integrity: sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==} hasBin: true @@ -6352,9 +6248,6 @@ packages: electron-to-chromium@1.5.334: resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==} - elliptic@6.6.1: - resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - emoji-regex-xs@2.0.1: resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} engines: {node: '>=10.0.0'} @@ -6486,23 +6379,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - ethereum-bloom-filters@1.2.0: - resolution: {integrity: sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==} - - ethereum-cryptography@1.2.0: - resolution: {integrity: sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==} - - ethereum-cryptography@2.2.1: - resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} - ethers@6.16.0: resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} engines: {node: '>=14.0.0'} - ethjs-unit@0.1.6: - resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} - engines: {node: '>=6.5.0', npm: '>=3'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -6707,9 +6587,6 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - fp-ts@2.16.9: - resolution: {integrity: sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==} - framer-motion@12.38.0: resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} peerDependencies: @@ -6856,9 +6733,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -6867,9 +6741,6 @@ packages: resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} engines: {node: '>=6'} - hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hono@4.12.12: resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} engines: {node: '>=16.9.0'} @@ -6980,11 +6851,6 @@ packages: resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} engines: {node: '>=10.13.0'} - io-ts@2.0.1: - resolution: {integrity: sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==} - peerDependencies: - fp-ts: ^2.0.0 - ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -7027,10 +6893,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-hex-prefixed@1.0.0: - resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==} - engines: {node: '>=6.5.0', npm: '>=3'} - is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -7167,9 +7029,6 @@ packages: react: optional: true - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -7265,9 +7124,6 @@ packages: resolution: {integrity: sha512-SU3lgh0rPvq7upc6vvdVrCsSMUG1h3ChvHVOY7wJ2fw4C9QEB7X3d5eyYEyULUX7UQtxZJtZXGuT6U2US72UYA==} engines: {node: '>=20.0.0'} - libphonenumber-js@1.12.40: - resolution: {integrity: sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg==} - lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -7303,24 +7159,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -7456,9 +7316,6 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - micro-ftch@0.3.1: - resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -7500,12 +7357,6 @@ packages: resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} @@ -7698,10 +7549,6 @@ packages: encoding: optional: true - node-forge@1.4.0: - resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} - engines: {node: '>= 6.13.0'} - node-gyp-build-optional-packages@5.1.1: resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} hasBin: true @@ -7731,10 +7578,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - number-to-bn@1.7.0: - resolution: {integrity: sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==} - engines: {node: '>=6.5.0', npm: '>=3'} - nypm@0.6.5: resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==} engines: {node: '>=18'} @@ -8157,9 +8000,6 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -8246,9 +8086,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - readonly-date@1.0.0: - resolution: {integrity: sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==} - real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -8632,10 +8469,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-hex-prefix@1.0.0: - resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==} - engines: {node: '>=6.5.0', npm: '>=3'} - strip-json-comments@5.0.3: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} @@ -8934,9 +8767,6 @@ packages: unctx@2.5.0: resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -9079,9 +8909,6 @@ packages: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} - utf8@3.0.0: - resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -9228,14 +9055,6 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - web3-eth-abi@1.10.4: - resolution: {integrity: sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ==} - engines: {node: '>=8.0.0'} - - web3-utils@1.10.4: - resolution: {integrity: sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==} - engines: {node: '>=8.0.0'} - webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -9391,9 +9210,6 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - xstate@5.28.0: - resolution: {integrity: sha512-Iaqq6ZrUzqeUtA3hC5LQKZfR8ZLzEFTImMHJM3jWEdVvXWdKvvVLXZEiNQWm3SCA9ZbEou/n5rcsna1wb9t28A==} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -10131,38 +9947,8 @@ snapshots: '@cbor-extract/cbor-extract-win32-x64@2.2.2': optional: true - '@celo/base@7.0.4': {} - - '@celo/utils@8.0.3': - dependencies: - '@celo/base': 7.0.4 - '@ethereumjs/rlp': 5.0.2 - '@ethereumjs/util': 8.0.5 - '@noble/ciphers': 1.1.3 - '@noble/curves': 1.3.0 - '@noble/hashes': 1.3.3 - '@types/bn.js': 5.2.0 - '@types/node': 18.19.130 - bignumber.js: 9.3.1 - fp-ts: 2.16.9 - io-ts: 2.0.1(fp-ts@2.16.9) - web3-eth-abi: 1.10.4 - web3-utils: 1.10.4 - '@cfworker/json-schema@4.1.1': {} - '@chainsafe/as-sha256@0.3.1': {} - - '@chainsafe/persistent-merkle-tree@0.4.2': - dependencies: - '@chainsafe/as-sha256': 0.3.1 - - '@chainsafe/ssz@0.9.4': - dependencies: - '@chainsafe/as-sha256': 0.3.1 - '@chainsafe/persistent-merkle-tree': 0.4.2 - case: 1.6.3 - '@chevrotain/cst-dts-gen@10.5.0': dependencies: '@chevrotain/gast': 10.5.0 @@ -10226,12 +10012,6 @@ snapshots: - typescript - utf-8-validate - '@cosmjs/encoding@0.32.4': - dependencies: - base64-js: 1.5.1 - bech32: 1.1.4 - readonly-date: 1.0.0 - '@csstools/color-helpers@6.0.2': {} '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -10597,150 +10377,6 @@ snapshots: '@esbuild/win32-x64@0.27.7': optional: true - '@ethereumjs/rlp@4.0.1': {} - - '@ethereumjs/rlp@5.0.2': {} - - '@ethereumjs/util@8.0.5': - dependencies: - '@chainsafe/ssz': 0.9.4 - '@ethereumjs/rlp': 4.0.1 - ethereum-cryptography: 1.2.0 - - '@ethereumjs/util@8.1.0': - dependencies: - '@ethereumjs/rlp': 4.0.1 - ethereum-cryptography: 2.2.1 - micro-ftch: 0.3.1 - - '@ethereumjs/util@9.1.0': - dependencies: - '@ethereumjs/rlp': 5.0.2 - ethereum-cryptography: 2.2.1 - - '@ethersproject/abi@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/abstract-provider@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/transactions': 5.8.0 - '@ethersproject/web': 5.8.0 - - '@ethersproject/abstract-signer@5.8.0': - dependencies: - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - - '@ethersproject/address@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/rlp': 5.8.0 - - '@ethersproject/base64@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - - '@ethersproject/bignumber@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - bn.js: 5.2.3 - - '@ethersproject/bytes@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/constants@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - - '@ethersproject/hash@5.8.0': - dependencies: - '@ethersproject/abstract-signer': 5.8.0 - '@ethersproject/address': 5.8.0 - '@ethersproject/base64': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - - '@ethersproject/keccak256@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - js-sha3: 0.8.0 - - '@ethersproject/logger@5.8.0': {} - - '@ethersproject/networks@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/properties@5.8.0': - dependencies: - '@ethersproject/logger': 5.8.0 - - '@ethersproject/rlp@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/signing-key@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - bn.js: 5.2.3 - elliptic: 6.6.1 - hash.js: 1.1.7 - - '@ethersproject/strings@5.8.0': - dependencies: - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - - '@ethersproject/transactions@5.8.0': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/rlp': 5.8.0 - '@ethersproject/signing-key': 5.8.0 - - '@ethersproject/web@5.8.0': - dependencies: - '@ethersproject/base64': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@exodus/bytes@1.15.0(@noble/hashes@2.0.1)': optionalDependencies: '@noble/hashes': 2.0.1 @@ -10772,61 +10408,6 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@getpara/core-sdk@2.20.0': - dependencies: - '@celo/utils': 8.0.3 - '@cosmjs/encoding': 0.32.4 - '@ethereumjs/util': 9.1.0 - '@getpara/user-management-client': 2.20.0 - '@noble/hashes': 1.8.0 - axios: 1.15.0 - base64url: 3.0.1 - elliptic: 6.6.1 - libphonenumber-js: 1.12.40 - node-forge: 1.4.0 - uuid: 11.1.0 - xstate: 5.28.0 - transitivePeerDependencies: - - debug - - '@getpara/ethers-v6-integration@2.20.0(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': - dependencies: - '@getpara/core-sdk': 2.20.0 - ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - debug - - '@getpara/server-sdk@2.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(viem@2.47.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6))': - dependencies: - '@getpara/core-sdk': 2.20.0 - '@getpara/user-management-client': 2.20.0 - '@getpara/viem-v2-integration': 2.20.0(viem@2.47.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6)) - uuid: 11.1.0 - ws: 8.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - debug - - utf-8-validate - - viem - - '@getpara/shared@1.14.0': {} - - '@getpara/user-management-client@2.20.0': - dependencies: - '@getpara/shared': 1.14.0 - axios: 1.15.0 - axios-retry: 4.5.0(axios@1.15.0) - libphonenumber-js: 1.12.40 - transitivePeerDependencies: - - debug - - '@getpara/viem-v2-integration@2.20.0(viem@2.47.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6))': - dependencies: - '@getpara/core-sdk': 2.20.0 - viem: 2.47.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6) - transitivePeerDependencies: - - debug - '@graphile/logger@0.2.0': {} '@graphql-typed-document-node/core@3.2.0(graphql@15.10.1)': @@ -11335,8 +10916,6 @@ snapshots: '@next/swc-win32-x64-msvc@16.2.2': optional: true - '@noble/ciphers@1.1.3': {} - '@noble/ciphers@1.3.0': {} '@noble/ciphers@2.1.1': {} @@ -11345,10 +10924,6 @@ snapshots: dependencies: '@noble/hashes': 1.3.2 - '@noble/curves@1.3.0': - dependencies: - '@noble/hashes': 1.3.3 - '@noble/curves@1.4.2': dependencies: '@noble/hashes': 1.4.0 @@ -11369,12 +10944,8 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 - '@noble/hashes@1.2.0': {} - '@noble/hashes@1.3.2': {} - '@noble/hashes@1.3.3': {} - '@noble/hashes@1.4.0': {} '@noble/hashes@1.7.0': {} @@ -11383,8 +10954,6 @@ snapshots: '@noble/hashes@2.0.1': {} - '@noble/secp256k1@1.7.1': {} - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -12905,38 +12474,14 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.1': optional: true - '@scure/base@1.1.9': {} - '@scure/base@1.2.6': {} - '@scure/bip32@1.1.5': - dependencies: - '@noble/hashes': 1.2.0 - '@noble/secp256k1': 1.7.1 - '@scure/base': 1.1.9 - - '@scure/bip32@1.4.0': - dependencies: - '@noble/curves': 1.4.2 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.9 - '@scure/bip32@1.7.0': dependencies: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 - '@scure/bip39@1.1.1': - dependencies: - '@noble/hashes': 1.2.0 - '@scure/base': 1.1.9 - - '@scure/bip39@1.3.0': - dependencies: - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.9 - '@scure/bip39@1.6.0': dependencies: '@noble/hashes': 1.8.0 @@ -14295,10 +13840,6 @@ snapshots: tslib: 2.8.1 optional: true - '@types/bn.js@5.2.0': - dependencies: - '@types/node': 24.12.2 - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -14388,10 +13929,6 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@18.19.130': - dependencies: - undici-types: 5.26.5 - '@types/node@22.19.15': dependencies: undici-types: 6.21.0 @@ -15586,15 +15123,11 @@ snapshots: base64-js@1.5.1: {} - base64url@3.0.1: {} - baseline-browser-mapping@2.10.10: {} baseline-browser-mapping@2.10.17: {} - bech32@1.1.4: {} - - better-auth@1.5.6(deahontvrs7v3pbjeoeq75dmbm): + better-auth@1.5.6(6d6bc65b4e207ce0ccf1adc5fe53a0f6): dependencies: '@better-auth/core': 1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.2.0) '@better-auth/drizzle-adapter': 1.5.6(@better-auth/core@1.5.6(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.2(zod@4.3.6))(jose@6.2.2)(kysely@0.28.14)(nanostores@1.2.0))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.2(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.1)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.14)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.9)(prisma@7.4.2(@types/react@19.2.14)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))) @@ -15642,8 +15175,6 @@ snapshots: dependencies: require-from-string: 2.0.2 - bignumber.js@9.3.1: {} - bin-version-check@5.1.0: dependencies: bin-version: 6.0.0 @@ -15721,8 +15252,6 @@ snapshots: dependencies: fill-range: 7.1.1 - brorand@1.1.0: {} - browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.10 @@ -15849,8 +15378,6 @@ snapshots: caniuse-lite@1.0.30001787: {} - case@1.6.3: {} - cbor-extract@2.2.2: dependencies: node-gyp-build-optional-packages: 5.1.1 @@ -16275,16 +15802,6 @@ snapshots: electron-to-chromium@1.5.334: {} - elliptic@6.6.1: - dependencies: - bn.js: 5.2.3 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - emoji-regex-xs@2.0.1: {} emoji-regex@10.6.0: {} @@ -16486,24 +16003,6 @@ snapshots: etag@1.8.1: {} - ethereum-bloom-filters@1.2.0: - dependencies: - '@noble/hashes': 1.8.0 - - ethereum-cryptography@1.2.0: - dependencies: - '@noble/hashes': 1.2.0 - '@noble/secp256k1': 1.7.1 - '@scure/bip32': 1.1.5 - '@scure/bip39': 1.1.1 - - ethereum-cryptography@2.2.1: - dependencies: - '@noble/curves': 1.4.2 - '@noble/hashes': 1.4.0 - '@scure/bip32': 1.4.0 - '@scure/bip39': 1.3.0 - ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@adraffy/ens-normalize': 1.10.1 @@ -16517,11 +16016,6 @@ snapshots: - bufferutil - utf-8-validate - ethjs-unit@0.1.6: - dependencies: - bn.js: 5.2.3 - number-to-bn: 1.7.0 - eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} @@ -16807,8 +16301,6 @@ snapshots: forwarded@0.2.0: {} - fp-ts@2.16.9: {} - framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: motion-dom: 12.38.0 @@ -16980,23 +16472,12 @@ snapshots: dependencies: has-symbols: 1.1.0 - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - hasown@2.0.2: dependencies: function-bind: 1.1.2 hex-rgb@4.3.0: {} - hmac-drbg@1.0.1: - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - hono@4.12.12: {} hpagent@1.2.0: {} @@ -17120,10 +16601,6 @@ snapshots: interpret@3.1.1: {} - io-ts@2.0.1(fp-ts@2.16.9): - dependencies: - fp-ts: 2.16.9 - ip-address@10.1.0: {} ipaddr.js@1.9.1: {} @@ -17148,8 +16625,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-hex-prefixed@1.0.0: {} - is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -17270,8 +16745,6 @@ snapshots: '@types/react': 19.2.14 react: 19.2.4 - js-sha3@0.8.0: {} - js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -17374,8 +16847,6 @@ snapshots: kysely@0.28.14: {} - libphonenumber-js@1.12.40: {} - lightningcss-android-arm64@1.32.0: optional: true @@ -17520,8 +16991,6 @@ snapshots: methods@1.1.2: {} - micro-ftch@0.3.1: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -17549,10 +17018,6 @@ snapshots: mimic-response@4.0.0: {} - minimalistic-assert@1.0.1: {} - - minimalistic-crypto-utils@1.0.1: {} - minimatch@10.2.4: dependencies: brace-expansion: 5.0.5 @@ -17702,8 +17167,6 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-forge@1.4.0: {} - node-gyp-build-optional-packages@5.1.1: dependencies: detect-libc: 2.1.2 @@ -17726,11 +17189,6 @@ snapshots: dependencies: path-key: 3.1.1 - number-to-bn@1.7.0: - dependencies: - bn.js: 5.2.3 - strip-hex-prefix: 1.0.0 - nypm@0.6.5: dependencies: citty: 0.2.1 @@ -18254,10 +17712,6 @@ snapshots: radix3@1.1.2: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - range-parser@1.2.1: {} raw-body@2.5.3: @@ -18338,8 +17792,6 @@ snapshots: readdirp@5.0.0: {} - readonly-date@1.0.0: {} - real-require@0.2.0: {} recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1): @@ -18849,10 +18301,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-hex-prefix@1.0.0: - dependencies: - is-hex-prefixed: 1.0.0 - strip-json-comments@5.0.3: {} stripe@20.4.1(@types/node@24.12.2): @@ -19141,8 +18589,6 @@ snapshots: magic-string: 0.30.21 unplugin: 2.3.11 - undici-types@5.26.5: {} - undici-types@6.19.8: {} undici-types@6.21.0: {} @@ -19235,8 +18681,6 @@ snapshots: node-gyp-build: 4.8.4 optional: true - utf8@3.0.0: {} - utils-merge@1.0.1: {} uuid@10.0.0: {} @@ -19380,22 +18824,6 @@ snapshots: defaults: 1.0.4 optional: true - web3-eth-abi@1.10.4: - dependencies: - '@ethersproject/abi': 5.8.0 - web3-utils: 1.10.4 - - web3-utils@1.10.4: - dependencies: - '@ethereumjs/util': 8.1.0 - bn.js: 5.2.3 - ethereum-bloom-filters: 1.2.0 - ethereum-cryptography: 2.2.1 - ethjs-unit: 0.1.6 - number-to-bn: 1.7.0 - randombytes: 2.1.0 - utf8: 3.0.0 - webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: @@ -19557,8 +18985,6 @@ snapshots: xmlchars@2.2.0: {} - xstate@5.28.0: {} - xtend@4.0.2: {} y18n@5.0.8: {} diff --git a/scripts/miscellaneous/fund-test-wallet.ts b/scripts/miscellaneous/fund-test-wallet.ts deleted file mode 100644 index 9331434c1..000000000 --- a/scripts/miscellaneous/fund-test-wallet.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Fund the persistent test wallet on Sepolia - * - * Sends testnet ETH from a funder EOA to the persistent test org's Para wallet. - * Skips if the wallet already has sufficient balance. - * - * Requires: - * TESTNET_FUNDER_PK - Private key of a funded Sepolia EOA (in .env) - * DATABASE_URL - Database connection string (in .env) - * - * Run with: pnpm db:fund-test-wallet - */ - -import "dotenv/config"; -import { eq } from "drizzle-orm"; -import { drizzle } from "drizzle-orm/postgres-js"; -import { ethers } from "ethers"; -import postgres from "postgres"; -import { getDatabaseUrl } from "../../lib/db/connection-utils"; -import { chains, organization, paraWallets } from "../../lib/db/schema"; - -const TEST_ORG_SLUG = "e2e-test-org"; -const SEPOLIA_CHAIN_ID = 11_155_111; -const FUNDING_AMOUNT = ethers.parseEther("0.002"); -const MIN_BALANCE = ethers.parseEther("0.001"); - -async function main(): Promise { - const funderPk = process.env.TESTNET_FUNDER_PK; - if (!funderPk) { - console.error("TESTNET_FUNDER_PK environment variable is required"); - process.exit(1); - } - - const databaseUrl = getDatabaseUrl(); - const client = postgres(databaseUrl, { max: 1 }); - const db = drizzle(client); - - try { - // Look up test org - const [testOrg] = await db - .select() - .from(organization) - .where(eq(organization.slug, TEST_ORG_SLUG)) - .limit(1); - - if (!testOrg) { - console.error( - `Test org "${TEST_ORG_SLUG}" not found. Run pnpm db:seed-test-wallet first.` - ); - process.exit(1); - } - - // Look up Para wallet - const [wallet] = await db - .select() - .from(paraWallets) - .where(eq(paraWallets.organizationId, testOrg.id)) - .limit(1); - - if (!wallet) { - console.error( - "No Para wallet for test org. Run pnpm db:seed-test-wallet first." - ); - process.exit(1); - } - - const walletAddress = wallet.walletAddress; - console.log(`Test wallet: ${walletAddress}`); - - // Look up Sepolia RPC from chains table - const [sepoliaChain] = await db - .select() - .from(chains) - .where(eq(chains.chainId, SEPOLIA_CHAIN_ID)) - .limit(1); - - if (!sepoliaChain) { - console.error( - "Sepolia chain not found in DB. Run pnpm db:seed-chains first." - ); - process.exit(1); - } - - const provider = new ethers.JsonRpcProvider(sepoliaChain.defaultPrimaryRpc); - const funder = new ethers.Wallet(funderPk, provider); - - console.log(`Funder: ${funder.address}`); - console.log(`RPC: ${sepoliaChain.defaultPrimaryRpc}`); - - // Check funder balance - const funderBalance = await provider.getBalance(funder.address); - console.log(`Funder balance: ${ethers.formatEther(funderBalance)} ETH`); - - if (funderBalance < FUNDING_AMOUNT) { - console.error( - `Funder has insufficient balance (${ethers.formatEther(funderBalance)} ETH). ` + - `Need at least ${ethers.formatEther(FUNDING_AMOUNT)} ETH.` - ); - process.exit(1); - } - - // Check test wallet balance - const walletBalance = await provider.getBalance(walletAddress); - console.log(`Wallet balance: ${ethers.formatEther(walletBalance)} ETH`); - - if (walletBalance >= MIN_BALANCE) { - console.log( - `Wallet already has sufficient balance (>= ${ethers.formatEther(MIN_BALANCE)} ETH). Skipping.` - ); - return; - } - - // Fund the wallet - console.log( - `Sending ${ethers.formatEther(FUNDING_AMOUNT)} ETH to ${walletAddress}...` - ); - const tx = await funder.sendTransaction({ - to: walletAddress, - value: FUNDING_AMOUNT, - }); - console.log(`TX hash: ${tx.hash}`); - - const receipt = await tx.wait(); - console.log( - `Confirmed in block ${receipt?.blockNumber}. Gas used: ${receipt?.gasUsed.toString()}` - ); - - // Verify new balance - const newBalance = await provider.getBalance(walletAddress); - console.log(`New wallet balance: ${ethers.formatEther(newBalance)} ETH`); - } finally { - await client.end(); - } -} - -main().catch((err: unknown) => { - console.error( - "Failed to fund test wallet:", - err instanceof Error ? err.message : err - ); - process.exit(1); -}); diff --git a/scripts/seed/seed-test-wallet.ts b/scripts/seed/seed-test-wallet.ts deleted file mode 100644 index a9b70509a..000000000 --- a/scripts/seed/seed-test-wallet.ts +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Seed script for persistent E2E test account - * - * Seeds a test user (with login credentials) + organization + Para wallet - * for write-contract E2E tests and Playwright tests. - * Idempotent: skips records that already exist. - * - * The wallet data is hardcoded from the pre-provisioned Para wallet - * (same wallet used by keeper-app). This avoids calling the Para API - * at seed time and ensures deterministic wallet addresses across CI runs. - * - * Test credentials: - * Email: pr-test-do-not-delete@techops.services - * Password: TestPassword123! - * - * Environment variables: - * DATABASE_URL - PostgreSQL connection string (required) - * TEST_WALLET_ENCRYPTION_KEY - 32-byte hex key for encrypting user share (required for wallet) - * TEST_PARA_USER_SHARE - Raw Para user share base64 string (required for wallet) - * - * Run with: pnpm db:seed-test-wallet - */ - -import dotenv from "dotenv"; -import { expand } from "dotenv-expand"; - -expand(dotenv.config()); - -import { hashPassword } from "better-auth/crypto"; -import { and, eq, sql } from "drizzle-orm"; -import { drizzle } from "drizzle-orm/postgres-js"; -import postgres from "postgres"; -import { encryptUserShare } from "@/lib/encryption"; -import { getDatabaseUrl } from "../../lib/db/connection-utils"; -import { - accounts, - member, - organization, - paraWallets, - users, -} from "../../lib/db/schema"; -import { generateId } from "../../lib/utils/id"; - -const TEST_ORG_SLUG = "e2e-test-org"; -const TEST_USER_EMAIL = "pr-test-do-not-delete@techops.services"; -const TEST_PASSWORD = "TestPassword123!"; - -// Hardcoded wallet data from pre-provisioned Para wallet -// Same wallet used by keeper-app (KeeperHub Staging partner) -const TEST_WALLET_ID = "3b1acc96-170f-4148-800b-7bca3e2ee6ad"; -const TEST_WALLET_ADDRESS = "0x4f1089424dcf25b1290631df483a436b320e51a1"; - -type Db = ReturnType; - -async function ensureUser(db: Db): Promise { - // Case-insensitive lookup: the email was previously seeded as uppercase - const existing = await db - .select() - .from(users) - .where(sql`lower(${users.email}) = ${TEST_USER_EMAIL}`) - .limit(1); - - if (existing.length > 0) { - // Normalize to lowercase if stored as uppercase - if (existing[0].email !== TEST_USER_EMAIL) { - await db - .update(users) - .set({ email: TEST_USER_EMAIL }) - .where(eq(users.id, existing[0].id)); - console.log(`Normalized test user email to lowercase (id: ${existing[0].id})`); - } else { - console.log(`Test user already exists (id: ${existing[0].id})`); - } - return existing[0].id; - } - - const userId = generateId(); - await db.insert(users).values({ - id: userId, - name: "E2E Test User", - email: TEST_USER_EMAIL, - emailVerified: true, - createdAt: new Date(), - updatedAt: new Date(), - }); - console.log(`Created test user (id: ${userId})`); - return userId; -} - -async function ensureCredentialAccount(db: Db, userId: string): Promise { - const existing = await db - .select() - .from(accounts) - .where( - and(eq(accounts.userId, userId), eq(accounts.providerId, "credential")) - ) - .limit(1); - - if (existing.length > 0) { - console.log("Credential account already exists"); - return; - } - - const hashedPassword = await hashPassword(TEST_PASSWORD); - await db.insert(accounts).values({ - id: generateId(), - accountId: userId, - providerId: "credential", - userId, - password: hashedPassword, - createdAt: new Date(), - updatedAt: new Date(), - }); - console.log(`Created credential account (password: ${TEST_PASSWORD})`); -} - -async function ensureOrganization(db: Db, userId: string): Promise { - const existing = await db - .select() - .from(organization) - .where(eq(organization.slug, TEST_ORG_SLUG)) - .limit(1); - - if (existing.length > 0) { - const orgId = existing[0].id; - console.log(`Test org already exists (id: ${orgId})`); - - // Ensure member record exists for this user (may be missing if user was re-created) - const existingMember = await db - .select() - .from(member) - .where(and(eq(member.organizationId, orgId), eq(member.userId, userId))) - .limit(1); - - if (existingMember.length === 0) { - const memberId = generateId(); - await db.insert(member).values({ - id: memberId, - organizationId: orgId, - userId, - role: "owner", - createdAt: new Date(), - }); - console.log(`Created missing member record (id: ${memberId})`); - } - - return orgId; - } - - const orgId = generateId(); - await db.insert(organization).values({ - id: orgId, - name: "E2E Test Organization", - slug: TEST_ORG_SLUG, - createdAt: new Date(), - }); - console.log(`Created test org (id: ${orgId})`); - - const memberId = generateId(); - await db.insert(member).values({ - id: memberId, - organizationId: orgId, - userId, - role: "owner", - createdAt: new Date(), - }); - console.log(`Created member record (id: ${memberId})`); - return orgId; -} - -async function ensureParaWallet( - db: Db, - userId: string, - orgId: string -): Promise { - const existing = await db - .select() - .from(paraWallets) - .where(eq(paraWallets.organizationId, orgId)) - .limit(1); - - if (existing.length > 0) { - console.log(`Wallet already exists: ${existing[0].walletAddress}`); - return; - } - - const rawUserShare = process.env.TEST_PARA_USER_SHARE; - if (!rawUserShare) { - console.log( - "TEST_PARA_USER_SHARE not set, skipping wallet seed. " + - "Wallet-dependent tests will be skipped." - ); - return; - } - - if (!process.env.WALLET_ENCRYPTION_KEY) { - console.log( - "WALLET_ENCRYPTION_KEY not set, skipping wallet seed. " + - "Wallet-dependent tests will be skipped." - ); - return; - } - - const encryptedShare = encryptUserShare(rawUserShare); - - await db.insert(paraWallets).values({ - id: generateId(), - userId, - organizationId: orgId, - provider: "para", - email: TEST_USER_EMAIL, - paraWalletId: TEST_WALLET_ID, - walletAddress: TEST_WALLET_ADDRESS, - userShare: encryptedShare, - }); - - console.log(`Created wallet: ${TEST_WALLET_ADDRESS}`); -} - -function assertNotProduction(): void { - if (process.env.NODE_ENV === "production") { - throw new Error( - "Refusing to seed test account: NODE_ENV=production. " + - "Set ALLOW_SEED_TEST_WALLET=true to override." - ); - } - - const dbUrl = process.env.DATABASE_URL ?? ""; - try { - const parsed = new URL(dbUrl); - const host = parsed.hostname; - const isLocal = - host === "localhost" || - host === "127.0.0.1" || - host === "" || - host.endsWith(".svc.cluster.local") || - host.endsWith(".internal"); - - if (!isLocal && process.env.ALLOW_SEED_TEST_WALLET !== "true") { - throw new Error( - `Refusing to seed test account: DATABASE_URL host "${host}" looks like a remote database. ` + - "Set ALLOW_SEED_TEST_WALLET=true to override." - ); - } - } catch (error) { - if (error instanceof TypeError) { - return; - } - throw error; - } -} - -async function seedTestWallet(): Promise { - assertNotProduction(); - - const connectionString = getDatabaseUrl(); - console.log("Connecting to database..."); - - const client = postgres(connectionString, { max: 1 }); - const db = drizzle(client); - - try { - const userId = await ensureUser(db); - await ensureCredentialAccount(db, userId); - const orgId = await ensureOrganization(db, userId); - await ensureParaWallet(db, userId, orgId); - - const wallet = await db - .select() - .from(paraWallets) - .where(eq(paraWallets.organizationId, orgId)) - .limit(1); - - console.log("\nE2E test account ready:"); - console.log(` Email: ${TEST_USER_EMAIL}`); - console.log(` Password: ${TEST_PASSWORD}`); - console.log(` Org Slug: ${TEST_ORG_SLUG}`); - console.log(` Org ID: ${orgId}`); - console.log(` User ID: ${userId}`); - if (wallet.length > 0) { - console.log(` Wallet Address: ${wallet[0].walletAddress}`); - } - } finally { - await client.end(); - } -} - -seedTestWallet() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error seeding test wallet:", err); - process.exit(1); - }); diff --git a/scripts/seed/seed-x402-test.ts b/scripts/seed/seed-x402-test.ts index a4030de1c..dfb5376f6 100644 --- a/scripts/seed/seed-x402-test.ts +++ b/scripts/seed/seed-x402-test.ts @@ -13,7 +13,7 @@ import { accounts, member, organization, - paraWallets, + organizationWallets, users, workflows, } from "../../lib/db/schema"; @@ -141,8 +141,8 @@ async function ensureWallet( ): Promise { const existing = await db .select() - .from(paraWallets) - .where(eq(paraWallets.organizationId, orgId)) + .from(organizationWallets) + .where(eq(organizationWallets.organizationId, orgId)) .limit(1); if (existing.length > 0) { @@ -150,11 +150,10 @@ async function ensureWallet( return; } - await db.insert(paraWallets).values({ + await db.insert(organizationWallets).values({ id: generateId(), userId, organizationId: orgId, - provider: "turnkey", email: TEST_USER_EMAIL, walletAddress: TEST_WALLET_ADDRESS, turnkeySubOrgId: "test-sub-org", diff --git a/specs/web3/PARA_INTEGRATION.md b/specs/web3/PARA_INTEGRATION.md deleted file mode 100644 index 92b09eb21..000000000 --- a/specs/web3/PARA_INTEGRATION.md +++ /dev/null @@ -1,90 +0,0 @@ -# Para Wallet Integration - -## Overview - -This integration automatically creates and manages Para wallets for users, enabling server-side blockchain operations within workflow steps. - -## What This Does - -1. **Automatic Wallet Creation**: When a user signs up, a Para wallet is automatically created for them -2. **Secure Storage**: Wallet keyshares are encrypted with AES-256-GCM and stored securely in the database -3. **Server-Side Access**: Workflow steps can use the user's wallet to perform blockchain operations (sending transactions, signing messages, interacting with smart contracts, etc.) -4. **User Visibility**: Users can see their wallet address displayed in the user dropdown menu - -## How It Works - -### Wallet Creation Flow -1. **User signs up** → Better Auth triggers a database hook - - File: `lib/auth.ts` - - Hook: `databaseHooks.user.create.after` - -2. **Database hook calls Para SDK** to create a pregenerated wallet - - `const wallet = await paraClient.createPregenWallet({ type: "EVM", pregenId: { email } })` - - Returns: `{ id, address, ... }` - -3. **Wallet keyshare is encrypted** and stored in database - - File: `lib/encryption.ts` - - Function: `encryptUserShare(userShare)` - - Stored in: `para_wallets` table - -4. **Wallet address is displayed** to the user - - File: `components/workflows/user-menu.tsx` - - Shown in user dropdown menu - -### Workflow Step Usage -When a workflow step needs to use the wallet: -1. Fetch encrypted keyshare from database (using authenticated userId) -2. Decrypt the keyshare -3. Initialize Para signer for blockchain operations -4. Perform the operation (send transaction, sign message, call smart contract, etc.) - -## Key Components - -### Database -- **Table**: `para_wallets` -- **Fields**: userId (unique), email, walletId, walletAddress, encrypted userShare, createdAt -- **Security**: One wallet per user, foreign key to users table, cascading delete - -### Encryption -- **Algorithm**: AES-256-GCM (authenticated encryption) -- **Key Storage**: Environment variable `WALLET_ENCRYPTION_KEY` -- **Format**: `iv:authTag:encryptedData` (prevents tampering) - -### Helper Functions -- `getUserWallet(userId)` - Retrieve wallet from database -- `initializeParaSigner(userId, rpcUrl)` - Get ready-to-use signer for transactions -- `getUserWalletAddress(userId)` - Get wallet address for display -- `userHasWallet(userId)` - Check if user has wallet - -## What You Can Build - -Workflow steps that use the user's wallet can: -- Send ETH or ERC-20 tokens -- Interact with smart contracts (DeFi, NFTs, DAOs) -- Sign messages or data -- Check balances and token ownership -- Execute any blockchain operation the user authorizes - -## Key Files - -- `lib/auth.ts` - Better Auth configuration with wallet creation hook -- `lib/db/schema.ts` - Database schema for `para_wallets` table -- `lib/encryption.ts` - AES-256-GCM encryption/decryption utilities -- `lib/para/wallet-helpers.ts` - Helper functions for wallet operations -- `app/api/user/route.ts` - API endpoint that includes wallet address -- `components/workflows/user-menu.tsx` - UI component displaying wallet address - -## Environment Variables Required - -```env -PARA_API_KEY=your-para-api-key -PARA_ENVIRONMENT=beta # or 'prod' -WALLET_ENCRYPTION_KEY=64-character-hex-string -``` - -## Security Notes - -- All Para SDK operations happen server-side only -- userShare never transmitted to browser -- Encryption key stored only in environment variables -- Each user can only access their own wallet (enforced by userId authentication) diff --git a/specs/web3/PARA_WORKFLOW_STEP.md b/specs/web3/PARA_WORKFLOW_STEP.md deleted file mode 100644 index eb4d50d31..000000000 --- a/specs/web3/PARA_WORKFLOW_STEP.md +++ /dev/null @@ -1,44 +0,0 @@ -# Para Wallet - Send Sepolia ETH Workflow Step - -## Goal - -Create a workflow step that allows users to send ETH on the Sepolia testnet using their Para wallet. - -## File locations - -- Plugin definition: plugins/[name]/index.ts -- Step implementation: plugins/[name]/steps/[step-name].ts -- Step registry: lib/step-registry.ts (auto-generated) -- Workflow executor: lib/workflow-executor.workflow.ts -- Step handler utilities: lib/steps/step-handler.ts - -## For this step - -You'll need to: - -- Create plugins/para/index.ts — register the plugin -- Create plugins/para/steps/send-eth.ts — step implementation -- Access userId — look up from executionId via workflowExecutions table -- Use wallet helpers — initializeParaSigner(userId, rpcUrl) from lib/para/wallet-helpers.ts -- Define config fields — amount and recipientAddress in plugin registration -- The PARA integration already exists (wallet creation, encryption, helpers), so you're adding a step that uses it. - -## Current Status - -- ✅ Para wallet integration is complete (wallets auto-created on user signup) -- ✅ Wallet addresses display in user dropdown menu - -## What Works - -1. Users automatically get a Para wallet when they sign up -2. Wallet keyshares are encrypted and stored securely in the database - -## What Doesn't Work - -Nothing about the step is yet implemented - -## The Issue - -## Next Steps - -## Key Files diff --git a/tests/e2e/playwright/utils/cleanup.ts b/tests/e2e/playwright/utils/cleanup.ts index a0c083b99..6261e244c 100644 --- a/tests/e2e/playwright/utils/cleanup.ts +++ b/tests/e2e/playwright/utils/cleanup.ts @@ -6,69 +6,13 @@ const TEST_VERIFICATION_PATTERN = "%test+%@techops.services%"; // Persistent test account with testnet ETH - NEVER delete const PROTECTED_EMAIL = "pr-test-do-not-delete@techops.services"; -const PARA_API_BASE = "https://api.getpara.com"; - -type ParaPortalConfig = { - orgId: string; - projectId: string; - keyId: string; - apiKey: string; -}; - -function getParaPortalConfig(): ParaPortalConfig | null { - const orgId = process.env.PARA_PORTAL_ORG_ID; - const projectId = process.env.PARA_PORTAL_PROJECT_ID; - const keyId = process.env.PARA_PORTAL_KEY_ID; - const apiKey = process.env.PARA_PORTAL_API_KEY; - - if (!(orgId && projectId && keyId && apiKey)) { - return null; - } - - return { orgId, projectId, keyId, apiKey }; -} - -/** - * Delete a pregenerated wallet from the Para Portal API. - * Returns true if deleted (or already gone), false on failure. - */ -async function deleteParaPregenWallet( - config: ParaPortalConfig, - walletId: string -): Promise { - const url = `${PARA_API_BASE}/organizations/${config.orgId}/projects/${config.projectId}/beta/keys/${config.keyId}/pregen/${walletId}`; - - const response = await fetch(url, { - method: "DELETE", - headers: { - accept: "application/json", - origin: "https://developer.getpara.com", - "x-external-api-key": config.apiKey, - }, - }); - - // 200/204 = deleted, 404 = already gone - if (!response.ok && response.status !== 404) { - const body = await response.text().catch(() => ""); - process.stderr.write( - `[cleanup] Para DELETE ${walletId}: ${response.status} ${body}\n` - ); - return false; - } - return true; -} - /** * Remove all ephemeral test users and their associated data. * Targets users matching `test+*@techops.services` (never the persistent * `pr-test-do-not-delete@techops.services` account). * - * Deletes in FK-safe order: workflow data -> para wallets (API + DB) -> - * invitations -> members -> sessions -> accounts -> organizations -> - * users -> verifications. - * - * Para wallet API cleanup requires PARA_PORTAL_* env vars. If not - * configured, only the DB rows are deleted. + * Deletes in FK-safe order: workflow data -> wallets -> invitations -> + * members -> sessions -> accounts -> organizations -> users -> verifications. */ export async function cleanupTestUsers(): Promise { const databaseUrl = process.env.DATABASE_URL; @@ -123,36 +67,8 @@ export async function cleanupTestUsers(): Promise { `; } - // 2. Para wallets - delete from API first, then DB - const paraWallets = await sql` - SELECT para_wallet_id FROM para_wallets WHERE user_id IN ${sql(userIds)} - `; - - if (paraWallets.length > 0) { - const paraConfig = getParaPortalConfig(); - if (paraConfig) { - const results = await Promise.allSettled( - paraWallets - .filter((w) => w.para_wallet_id != null) - .map((w) => - deleteParaPregenWallet(paraConfig, w.para_wallet_id as string) - ) - ); - const failed = results.filter( - (r) => - r.status === "rejected" || (r.status === "fulfilled" && !r.value) - ); - if (failed.length > 0) { - process.stderr.write( - `[cleanup] ${failed.length}/${paraWallets.length} Para API deletions failed\n` - ); - } - } - - await sql` - DELETE FROM para_wallets WHERE user_id IN ${sql(userIds)} - `; - } + // 2. Wallets + await sql`DELETE FROM organization_wallets WHERE user_id IN ${sql(userIds)}`; // 3. Integrations, API keys, and org-scoped data await sql`DELETE FROM integrations WHERE user_id IN ${sql(userIds)}`; diff --git a/tests/e2e/vitest/transaction-flow.test.ts b/tests/e2e/vitest/transaction-flow.test.ts index cb52099db..e9b61b68a 100644 --- a/tests/e2e/vitest/transaction-flow.test.ts +++ b/tests/e2e/vitest/transaction-flow.test.ts @@ -51,8 +51,8 @@ const SEPOLIA_FALLBACK_RPC = getRpcUrlByChainId(11_155_111, "fallback"); // Build a failover-aware Sepolia manager. Each describe block reuses this // shape: construct a manager, take its current primary as the -// ethers.JsonRpcProvider that library calls (Para signer, NonceManager -// startSession, etc.) need, and route explicit reads in this test through +// ethers.JsonRpcProvider that library calls (NonceManager startSession, etc.) +// need, and route explicit reads in this test through // manager.executeWithFailover so a primary-endpoint hiccup falls back to // the configured secondary. async function buildSepoliaManager(): Promise { @@ -666,364 +666,3 @@ describe.skipIf(shouldSkip)("Transaction Flow with Real RPC", () => { .where(eq(walletLocks.walletAddress, ZERO_ADDRESS)); }, 30_000); }); - -/** - * Real Transaction Tests on Sepolia - * - * These tests send actual transactions on Sepolia testnet using - * the persistent test org's Para wallet (provisioned by pnpm db:seed-test-wallet). - * - * To run: - * pnpm test:e2e tests/e2e/transaction-flow.test.ts - */ -const TEST_ORG_SLUG = "e2e-test-org"; -const skipRealTx = - shouldSkip || - !process.env.PARA_API_KEY || - !process.env.TEST_PARA_USER_SHARE || - !!process.env.CI; - -describe.skipIf(skipRealTx)("Real Transaction Tests (Sepolia)", () => { - let client: ReturnType; - let db: ReturnType; - let sepoliaManager: RpcProviderManager; - let sepoliaProvider: ethers.JsonRpcProvider; - let wallet: ethers.AbstractSigner; - let walletAddress: string; - let testExecutionId: string; - - beforeAll(async () => { - // Connect to database - const connectionString = - process.env.DATABASE_URL ?? - "postgresql://postgres:postgres@localhost:5433/keeperhub"; - - client = postgres(connectionString, { max: 5 }); - db = drizzle(client); - - // Look up persistent test org - const { organization, paraWallets } = await import("@/lib/db/schema"); - const [testOrg] = await db - .select() - .from(organization) - .where(eq(organization.slug, TEST_ORG_SLUG)) - .limit(1); - - if (!testOrg) { - throw new Error( - `Test org "${TEST_ORG_SLUG}" not found. Run pnpm db:seed-test-wallet first.` - ); - } - - const [paraWallet] = await db - .select() - .from(paraWallets) - .where(eq(paraWallets.organizationId, testOrg.id)) - .limit(1); - - if (!paraWallet) { - throw new Error( - "No Para wallet for test org. Run pnpm db:seed-test-wallet first." - ); - } - - walletAddress = paraWallet.walletAddress; - - // Initialize Para signer - const { ParaEthersSigner } = await import("@getpara/ethers-v6-integration"); - const { Environment, Para: ParaServer } = await import( - "@getpara/server-sdk" - ); - const { decryptUserShare } = await import("@/lib/encryption"); - - const paraClient = new ParaServer( - process.env.PARA_ENVIRONMENT === "prod" - ? Environment.PROD - : Environment.BETA, - process.env.PARA_API_KEY ?? "" - ); - - if (!paraWallet.userShare) { - throw new Error("Test wallet missing userShare"); - } - const decryptedShare = decryptUserShare(paraWallet.userShare); - await paraClient.setUserShare(decryptedShare); - - sepoliaManager = await buildSepoliaManager(); - sepoliaProvider = sepoliaManager.getProvider(); - wallet = new ParaEthersSigner(paraClient as any, sepoliaProvider); - - console.log(`Test wallet (Para): ${walletAddress}`); - - // Check balance through failover so a primary-endpoint blip falls back - // to the configured secondary instead of failing setup. - const balance = await sepoliaManager.executeWithFailover((p) => - p.getBalance(walletAddress) - ); - console.log(`Balance: ${ethers.formatEther(balance)} ETH`); - - if (balance < ethers.parseEther("0.001")) { - console.warn( - "Low balance! Fund the persistent test wallet with pnpm db:fund-test-wallet" - ); - } - }, 60_000); - - afterAll(async () => { - // Cleanup test data - if (walletAddress) { - await db - .delete(pendingTransactions) - .where( - eq(pendingTransactions.walletAddress, walletAddress.toLowerCase()) - ); - await db - .delete(walletLocks) - .where(eq(walletLocks.walletAddress, walletAddress.toLowerCase())); - } - await client.end(); - }); - - beforeEach(async () => { - resetNonceManager(); - resetGasStrategy(); - testExecutionId = `test_realtx_${Date.now()}`; - - // Cleanup previous test data - if (walletAddress) { - await db - .delete(pendingTransactions) - .where( - eq(pendingTransactions.walletAddress, walletAddress.toLowerCase()) - ); - await db - .delete(walletLocks) - .where(eq(walletLocks.walletAddress, walletAddress.toLowerCase())); - } - }); - - it("should send real ETH transfer with nonce management", async () => { - const nonceManager = new NonceManager(); - const gasStrategy = new AdaptiveGasStrategy(); - - // Start nonce session - const { session, validation } = await nonceManager.startSession( - walletAddress, - TEST_CHAIN_ID, - testExecutionId, - sepoliaProvider as any - ); - - console.log(`Chain nonce: ${validation.chainNonce}`); - - // Get gas config - const gasConfig = await gasStrategy.getGasConfig( - sepoliaProvider, - BigInt(21_000), - TEST_CHAIN_ID - ); - - console.log("Gas config:", { - gasLimit: gasConfig.gasLimit.toString(), - maxFeePerGas: `${ethers.formatUnits(gasConfig.maxFeePerGas, "gwei")} gwei`, - maxPriorityFeePerGas: `${ethers.formatUnits(gasConfig.maxPriorityFeePerGas, "gwei")} gwei`, - }); - - // Get nonce from manager - const nonce = nonceManager.getNextNonce(session); - expect(nonce).toBe(validation.chainNonce); - - // Build transaction - const tx = { - to: walletAddress, // Send to self - value: ethers.parseEther("0.0001"), // 0.0001 ETH - nonce, - gasLimit: gasConfig.gasLimit, - maxFeePerGas: gasConfig.maxFeePerGas, - maxPriorityFeePerGas: gasConfig.maxPriorityFeePerGas, - chainId: TEST_CHAIN_ID, - type: 2, // EIP-1559 - }; - - // Send transaction - console.log("Sending transaction..."); - const sentTx = await wallet.sendTransaction(tx); - console.log(`Transaction hash: ${sentTx.hash}`); - - // Record in database - await nonceManager.recordTransaction( - session, - nonce, - sentTx.hash, - "test_workflow", - gasConfig.maxFeePerGas.toString() - ); - - // Verify recorded as pending - const pending = await db - .select() - .from(pendingTransactions) - .where(eq(pendingTransactions.txHash, sentTx.hash)); - - expect(pending.length).toBe(1); - expect(pending[0].status).toBe("pending"); - - // Wait for confirmation - console.log("Waiting for confirmation..."); - const receipt = await sentTx.wait(); - expect(receipt).not.toBeNull(); - console.log(`Confirmed in block: ${receipt?.blockNumber}`); - - // Mark as confirmed - await nonceManager.confirmTransaction(sentTx.hash); - - // Verify confirmed in database - const confirmed = await db - .select() - .from(pendingTransactions) - .where(eq(pendingTransactions.txHash, sentTx.hash)); - - expect(confirmed[0].status).toBe("confirmed"); - expect(confirmed[0].confirmedAt).not.toBeNull(); - - await nonceManager.endSession(session); - }, 120_000); // 2 minute timeout for real tx - - it("should handle multiple sequential transactions", async () => { - const nonceManager = new NonceManager(); - const gasStrategy = new AdaptiveGasStrategy(); - - const { session } = await nonceManager.startSession( - walletAddress, - TEST_CHAIN_ID, - testExecutionId, - sepoliaProvider as any - ); - - const txHashes: string[] = []; - - // Send 2 transactions sequentially - for (let i = 0; i < 2; i++) { - const nonce = nonceManager.getNextNonce(session); - console.log(`Transaction ${i + 1}: nonce ${nonce}`); - - const gasConfig = await gasStrategy.getGasConfig( - sepoliaProvider, - BigInt(21_000), - TEST_CHAIN_ID - ); - - const tx = { - to: walletAddress, - value: ethers.parseEther("0.00001"), - nonce, - gasLimit: gasConfig.gasLimit, - maxFeePerGas: gasConfig.maxFeePerGas, - maxPriorityFeePerGas: gasConfig.maxPriorityFeePerGas, - chainId: TEST_CHAIN_ID, - type: 2, - }; - - const sentTx = await wallet.sendTransaction(tx); - console.log(`Tx ${i + 1} hash: ${sentTx.hash}`); - txHashes.push(sentTx.hash); - - await nonceManager.recordTransaction( - session, - nonce, - sentTx.hash, - "multi_tx_test", - gasConfig.maxFeePerGas.toString() - ); - } - - // Wait for both to confirm - console.log("Waiting for confirmations..."); - for (const hash of txHashes) { - const receipt = await sepoliaProvider.waitForTransaction(hash, 1, 60_000); - expect(receipt).not.toBeNull(); - await nonceManager.confirmTransaction(hash); - } - - // Verify both confirmed in database - const confirmed = await db - .select() - .from(pendingTransactions) - .where( - and( - eq(pendingTransactions.executionId, testExecutionId), - eq(pendingTransactions.status, "confirmed") - ) - ); - - expect(confirmed.length).toBe(2); - console.log("Both transactions confirmed"); - - await nonceManager.endSession(session); - }, 180_000); // 3 minute timeout - - it("should reconcile transactions on new session", async () => { - const nonceManager = new NonceManager(); - const gasStrategy = new AdaptiveGasStrategy(); - - // First session - send a transaction - const { session: session1 } = await nonceManager.startSession( - walletAddress, - TEST_CHAIN_ID, - `${testExecutionId}_1`, - sepoliaProvider as any - ); - - const nonce = nonceManager.getNextNonce(session1); - const gasConfig = await gasStrategy.getGasConfig( - sepoliaProvider, - BigInt(21_000), - TEST_CHAIN_ID - ); - - const tx = { - to: walletAddress, - value: ethers.parseEther("0.00001"), - nonce, - gasLimit: gasConfig.gasLimit, - maxFeePerGas: gasConfig.maxFeePerGas, - maxPriorityFeePerGas: gasConfig.maxPriorityFeePerGas, - chainId: TEST_CHAIN_ID, - type: 2, - }; - - const sentTx = await wallet.sendTransaction(tx); - await nonceManager.recordTransaction(session1, nonce, sentTx.hash); - - // Don't explicitly confirm - leave as pending - await nonceManager.endSession(session1); - - // Wait for tx to confirm on chain - console.log("Waiting for transaction to confirm on chain..."); - await sentTx.wait(); - - // Start new session - should reconcile the pending transaction - resetNonceManager(); - const manager2 = new NonceManager(); - const { session: session2, validation } = await manager2.startSession( - walletAddress, - TEST_CHAIN_ID, - `${testExecutionId}_2`, - sepoliaProvider as any - ); - - // Should have reconciled the transaction - expect(validation.reconciledCount).toBe(1); - console.log(`Reconciled ${validation.reconciledCount} transaction(s)`); - - // Verify status updated in database - const reconciled = await db - .select() - .from(pendingTransactions) - .where(eq(pendingTransactions.txHash, sentTx.hash)); - - expect(reconciled[0].status).toBe("confirmed"); - - await manager2.endSession(session2); - }, 180_000); -}); diff --git a/tests/e2e/vitest/write-contract-workflow.test.ts b/tests/e2e/vitest/write-contract-workflow.test.ts index 2f7bc5f45..ad7d579a6 100644 --- a/tests/e2e/vitest/write-contract-workflow.test.ts +++ b/tests/e2e/vitest/write-contract-workflow.test.ts @@ -28,7 +28,7 @@ vi.mock("server-only", () => ({})); import { chains, organization, - paraWallets, + organizationWallets, workflowExecutionLogs, workflowExecutions, workflows, @@ -38,12 +38,12 @@ import type { RpcProviderManager } from "@/lib/rpc/providers"; import { generateId } from "@/lib/utils/id"; import type { WriteContractInput } from "@/plugins/web3/steps/write-contract"; -// Skip if infrastructure not available (requires DB + Para API for tx signing) +// Skip if infrastructure not available (requires DB + Turnkey for tx signing) const shouldSkip = !( process.env.DATABASE_URL && - process.env.PARA_API_KEY && - process.env.TEST_PARA_USER_SHARE + process.env.TURNKEY_API_PUBLIC_KEY && + process.env.TURNKEY_API_PRIVATE_KEY ) || process.env.SKIP_INFRA_TESTS === "true"; // SimpleStorage contract on Sepolia @@ -135,8 +135,8 @@ describe.skipIf(shouldSkip)("Write Contract Workflow E2E", () => { // Look up Para wallet const wallet = await db .select() - .from(paraWallets) - .where(eq(paraWallets.organizationId, orgId)) + .from(organizationWallets) + .where(eq(organizationWallets.organizationId, orgId)) .limit(1); if (wallet.length === 0) { diff --git a/tests/integration/eip7702-spike.d.ts b/tests/integration/eip7702-spike.d.ts deleted file mode 100644 index 9da4aeb0f..000000000 --- a/tests/integration/eip7702-spike.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare module "@getpara/server-sdk/dist/esm/wallet/privateKey.js" { - export function getPrivateKey( - ctx: unknown, - userId: string, - walletId: string, - userShare: string - ): Promise; -} diff --git a/tests/integration/eip7702-spike.test.ts b/tests/integration/eip7702-spike.test.ts deleted file mode 100644 index 4d7d1f62a..000000000 --- a/tests/integration/eip7702-spike.test.ts +++ /dev/null @@ -1,584 +0,0 @@ -/// - -import { afterAll, describe, expect, it, vi } from "vitest"; - -vi.mock("server-only", () => ({})); - -// Unmock the database -- setup.ts mocks @/lib/db globally, but this spike -// needs real DB access for Para wallet lookups -vi.unmock("@/lib/db"); - -import { getRpcProviderFromUrls } from "@/lib/rpc/provider-factory"; -import { getRpcUrlByChainId } from "@/lib/rpc/rpc-config"; - -// --------------------------------------------------------------------------- -// Environment gate -- entire suite skips if keys are missing -// --------------------------------------------------------------------------- -const PIMLICO_API_KEY = process.env.PIMLICO_API_KEY; -const PARA_API_KEY = process.env.PARA_API_KEY; - -const HAS_PIMLICO = Boolean(PIMLICO_API_KEY); -const HAS_PARA = Boolean(PARA_API_KEY); -const HAS_ALL = HAS_PIMLICO && HAS_PARA; - -// Base Sepolia -const CHAIN_ID = 84_532; - -// Build a failover-aware ethers provider for Base Sepolia. Used by every -// RPC read on the ethers code path so a primary-endpoint hiccup falls back -// to the secondary instead of failing the whole test. Note: viem-side -// callers (Pimlico bundler / publicClient below) do not go through this -- -// they use viem's own transport and Pimlico's bundler has independent -// retry semantics. -async function getBaseSepoliaManager(): ReturnType< - typeof getRpcProviderFromUrls -> { - return getRpcProviderFromUrls( - getRpcUrlByChainId(CHAIN_ID, "primary"), - getRpcUrlByChainId(CHAIN_ID, "fallback"), - CHAIN_ID, - "base-sepolia" - ); -} - -// Top-level regex patterns for lint compliance -const HEX_64_PATTERN = /^0x[a-f0-9]{64}$/; -const RAW_HEX_64_PATTERN = /^[a-f0-9]{64}$/i; - -// Pimlico's reference SimpleAccount7702 implementation (EntryPoint 0.8) -const SIMPLE_ACCOUNT_7702_ADDRESS = - "0xe6Cae83BdE06E4c305530e199D7217f42808555B"; - -// --------------------------------------------------------------------------- -// Scenario A: ethers type-4 serialization (pure unit test, no network) -// --------------------------------------------------------------------------- -describe("Scenario A: ethers type-4 serialization", () => { - it("builds a valid type-4 transaction with authorizationList", async () => { - const { ethers } = await import("ethers"); - - const wallet = ethers.Wallet.createRandom(); - const delegateAddress = "0x0000000000000000000000000000000000000001"; - - const authHash = ethers.hashAuthorization({ - chainId: CHAIN_ID, - address: delegateAddress, - nonce: 0, - }); - - expect(authHash).toMatch(HEX_64_PATTERN); - - const sig = wallet.signingKey.sign(authHash); - - const recovered = ethers.verifyAuthorization( - { chainId: CHAIN_ID, address: delegateAddress, nonce: 0 }, - { r: sig.r, s: sig.s, yParity: sig.yParity } - ); - expect(recovered.toLowerCase()).toBe(wallet.address.toLowerCase()); - - const tx = ethers.Transaction.from({ - type: 4, - chainId: CHAIN_ID, - to: wallet.address, - value: 0, - gasLimit: 21_000, - maxFeePerGas: ethers.parseUnits("1", "gwei"), - maxPriorityFeePerGas: ethers.parseUnits("1", "gwei"), - nonce: 0, - authorizationList: [ - { - chainId: BigInt(CHAIN_ID), - address: delegateAddress, - nonce: BigInt(0), - signature: ethers.Signature.from({ - r: sig.r, - s: sig.s, - yParity: sig.yParity, - }), - }, - ], - }); - - // Type-4 envelope starts with 0x04 - expect(tx.unsignedSerialized.startsWith("0x04")).toBe(true); - expect(tx.type).toBe(4); - expect(tx.authorizationList).toHaveLength(1); - }); - - it("hashAuthorization produces MAGIC || rlp([chainId, address, nonce])", async () => { - const { ethers } = await import("ethers"); - - const auth = { - chainId: CHAIN_ID, - address: SIMPLE_ACCOUNT_7702_ADDRESS, - nonce: 42, - }; - - const hash = ethers.hashAuthorization(auth); - - expect(hash).toHaveLength(66); - expect(hash).toMatch(HEX_64_PATTERN); - }); -}); - -// --------------------------------------------------------------------------- -// Scenario B: Para signer + type-4 transaction -// Skipped if PARA_API_KEY is not set. -// --------------------------------------------------------------------------- -describe.skipIf(!HAS_PARA)("Scenario B: Para signer accepts type-4 RLP", () => { - it("attempts to sign a type-4 transaction via ParaEthersSigner", { - timeout: 60_000, - }, async () => { - const { ethers } = await import("ethers"); - const { ParaEthersSigner } = await import("@getpara/ethers-v6-integration"); - const { Environment, Para: ParaServer } = await import( - "@getpara/server-sdk" - ); - const { decryptUserShare } = await import("@/lib/encryption"); - const { getOrganizationWallet } = await import("@/lib/para/wallet-helpers"); - - const orgId = process.env.TEST_ORG_ID; - if (!orgId) { - console.log( - "SKIP: TEST_ORG_ID not set -- cannot test Para type-4 signing" - ); - return; - } - - const wallet = await getOrganizationWallet(orgId); - - const paraClient = new ParaServer( - process.env.PARA_ENVIRONMENT === "prod" - ? Environment.PROD - : Environment.BETA, - PARA_API_KEY ?? "" - ); - - if (!wallet.userShare) { - throw new Error("Wallet missing userShare"); - } - const decryptedShare = decryptUserShare(wallet.userShare); - await paraClient.setUserShare(decryptedShare); - await paraClient.setUserId(wallet.userId); - - // ParaEthersSigner expects a single ethers.Provider; this test only asks - // the signer to sign (no broadcast), so attaching the failover manager's - // current primary provider is sufficient. If the test is later extended - // to broadcast, wrap the broadcast call in manager.executeWithFailover. - const manager = await getBaseSepoliaManager(); - const provider = manager.getProvider(); - const paraSigner = new ParaEthersSigner(paraClient as any, provider); - const signerAddress = await paraSigner.getAddress(); - - // Local key for authorization signing -- we're testing Para's ability - // to sign the outer type-4 tx, not auth tuple signing - const localWallet = ethers.Wallet.createRandom(); - const authHash = ethers.hashAuthorization({ - chainId: CHAIN_ID, - address: SIMPLE_ACCOUNT_7702_ADDRESS, - nonce: 0, - }); - const authSig = localWallet.signingKey.sign(authHash); - - const txRequest = { - type: 4, - chainId: CHAIN_ID, - to: signerAddress, - value: 0, - gasLimit: 100_000, - maxFeePerGas: ethers.parseUnits("1", "gwei"), - maxPriorityFeePerGas: ethers.parseUnits("1", "gwei"), - nonce: 0, - authorizationList: [ - { - chainId: CHAIN_ID, - address: SIMPLE_ACCOUNT_7702_ADDRESS, - nonce: 0, - signature: ethers.Signature.from({ - r: authSig.r, - s: authSig.s, - yParity: authSig.yParity, - }), - }, - ], - }; - - try { - const signedTx = await paraSigner.signTransaction(txRequest); - console.log("RESULT B: Para ACCEPTED type-4 transaction"); - console.log(" Signed tx length:", signedTx.length); - expect(signedTx).toBeTruthy(); - } catch (error: unknown) { - const message = error instanceof Error ? error.message : String(error); - console.log("RESULT B: Para REJECTED type-4 transaction"); - console.log(" Error:", message); - // Expected failure -- tells us Para cannot handle type-4 - // and we need the private key extraction path - expect(message).toBeTruthy(); - } - }); -}); - -// --------------------------------------------------------------------------- -// Scenario C: Private key extraction + full EIP-7702 sponsorship -// Skipped if either PIMLICO_API_KEY or PARA_API_KEY is missing. -// --------------------------------------------------------------------------- -describe.skipIf(!HAS_ALL)( - "Scenario C: Private key extraction + Pimlico sponsorship", - () => { - let extractedAddress: string | undefined; - let privateKeyHex: string | undefined; - - afterAll(() => { - if (privateKeyHex) { - privateKeyHex = undefined; - } - }); - - it("C.a: extracts private key from Para and verifies address match", { - timeout: 60_000, - }, async () => { - const { ethers } = await import("ethers"); - const { Environment, Para: ParaServer } = await import( - "@getpara/server-sdk" - ); - const { getPrivateKey } = await import( - "@getpara/server-sdk/dist/esm/wallet/privateKey.js" - ); - const { decryptUserShare } = await import("@/lib/encryption"); - const { getOrganizationWallet } = await import( - "@/lib/para/wallet-helpers" - ); - - const orgId = process.env.TEST_ORG_ID; - if (!orgId) { - console.log("SKIP: TEST_ORG_ID not set -- cannot test key extraction"); - return; - } - - const walletRecord = await getOrganizationWallet(orgId); - if (!(walletRecord.userShare && walletRecord.paraWalletId)) { - throw new Error("Wallet missing Para credentials"); - } - const decryptedShare = decryptUserShare(walletRecord.userShare); - - const paraEnv = - process.env.PARA_ENVIRONMENT === "prod" - ? Environment.PROD - : Environment.BETA; - - const paraClient = new ParaServer(paraEnv, PARA_API_KEY ?? ""); - await paraClient.setUserShare(decryptedShare); - await paraClient.setUserId(walletRecord.userId); - - // Use the Para client's internal ctx which has the HTTP client - // configured for proper server authentication - const ctx = (paraClient as unknown as Record).ctx; - - try { - privateKeyHex = await getPrivateKey( - ctx, - walletRecord.userId, - walletRecord.paraWalletId, - decryptedShare - ); - - expect(privateKeyHex).toMatch(RAW_HEX_64_PATTERN); - - const localWallet = new ethers.Wallet(`0x${privateKeyHex}`); - extractedAddress = localWallet.address; - - console.log("RESULT C.a: Private key extraction SUCCEEDED"); - console.log(" Extracted address:", extractedAddress); - console.log(" DB wallet address:", walletRecord.walletAddress); - console.log( - " Match:", - extractedAddress.toLowerCase() === - walletRecord.walletAddress.toLowerCase() - ); - - expect(extractedAddress.toLowerCase()).toBe( - walletRecord.walletAddress.toLowerCase() - ); - } catch (error: unknown) { - const message = error instanceof Error ? error.message : String(error); - console.log("RESULT C.a: Private key extraction FAILED"); - console.log(" Error:", message); - - // Para's MPC protocol requires server-side session auth. - // Pregen wallets created in a different session context will - // fail with "user must be authenticated". This is an - // environment/session issue, not a code issue -- getPrivateKey - // is a valid API that works with properly authenticated sessions. - if (message.includes("user must be authenticated")) { - console.log( - " NOTE: Para session auth required. Test wallet needs active session." - ); - console.log( - " This does NOT mean getPrivateKey is broken -- it needs", - "a wallet created in the current API key's session." - ); - return; - } - throw error; - } - }); - - it("C.b: signs EIP-7702 authorization tuple with extracted key", { - timeout: 30_000, - }, async () => { - if (!(privateKeyHex && extractedAddress)) { - console.log("SKIP: C.a did not produce a private key"); - return; - } - - const { ethers } = await import("ethers"); - - const manager = await getBaseSepoliaManager(); - const accountNonce = await manager.executeWithFailover((p) => - p.getTransactionCount(extractedAddress as string) - ); - - const authHash = ethers.hashAuthorization({ - chainId: CHAIN_ID, - address: SIMPLE_ACCOUNT_7702_ADDRESS, - nonce: accountNonce, - }); - - const wallet = new ethers.Wallet(`0x${privateKeyHex}`); - const sig = wallet.signingKey.sign(authHash); - - const recovered = ethers.verifyAuthorization( - { - chainId: CHAIN_ID, - address: SIMPLE_ACCOUNT_7702_ADDRESS, - nonce: accountNonce, - }, - { r: sig.r, s: sig.s, yParity: sig.yParity } - ); - - console.log("RESULT C.b: Authorization signing SUCCEEDED"); - console.log(" Account nonce:", accountNonce); - console.log(" Recovered address:", recovered); - console.log( - " Match:", - recovered.toLowerCase() === extractedAddress.toLowerCase() - ); - - expect(recovered.toLowerCase()).toBe(extractedAddress.toLowerCase()); - }); - - it("C.c: submits sponsored EIP-7702 tx via Pimlico (ERC-4337 hybrid)", { - timeout: 120_000, - }, async () => { - if (!(privateKeyHex && extractedAddress)) { - console.log("SKIP: C.a did not produce a private key"); - return; - } - - const { createPublicClient, http } = await import("viem"); - const { privateKeyToAccount } = await import("viem/accounts"); - const { baseSepolia } = await import("viem/chains"); - const { createSmartAccountClient } = await import("permissionless"); - const { to7702SimpleSmartAccount } = await import( - "permissionless/accounts" - ); - const { createPimlicoClient } = await import( - "permissionless/clients/pimlico" - ); - - const startTime = Date.now(); - - const viemAccount = privateKeyToAccount( - `0x${privateKeyHex}` as `0x${string}` - ); - - const publicClient = createPublicClient({ - chain: baseSepolia, - transport: http("https://base-sepolia-rpc.publicnode.com"), - }); - - const pimlicoUrl = `https://api.pimlico.io/v2/${CHAIN_ID}/rpc?apikey=${PIMLICO_API_KEY}`; - - const pimlicoClient = createPimlicoClient({ - transport: http(pimlicoUrl), - entryPoint: { - address: - "0x0000000071727De22E5E9d8BAf0edAc6f37da032" as `0x${string}`, - version: "0.7", - }, - }); - - try { - const smartAccount = await to7702SimpleSmartAccount({ - client: publicClient, - owner: viemAccount, - }); - - console.log(" Smart account address:", smartAccount.address); - console.log(" EOA address:", viemAccount.address); - console.log( - " Addresses match (expected for 7702):", - smartAccount.address.toLowerCase() === - viemAccount.address.toLowerCase() - ); - - const gasPrices = await pimlicoClient.getUserOperationGasPrice(); - - const smartAccountClient = createSmartAccountClient({ - account: smartAccount, - chain: baseSepolia, - bundlerTransport: http(pimlicoUrl), - paymaster: pimlicoClient, - userOperation: { - estimateFeesPerGas: async () => gasPrices.fast, - }, - }); - - const balanceBefore = await publicClient.getBalance({ - address: viemAccount.address, - }); - - const txHash = await smartAccountClient.sendTransaction({ - to: viemAccount.address, - value: BigInt(0), - data: "0x" as `0x${string}`, - }); - - const receipt = await publicClient.waitForTransactionReceipt({ - hash: txHash, - }); - - const balanceAfter = await publicClient.getBalance({ - address: viemAccount.address, - }); - - const latencyMs = Date.now() - startTime; - const gasUsed = receipt.gasUsed; - const effectiveGasPrice = receipt.effectiveGasPrice; - const gasCostWei = gasUsed * effectiveGasPrice; - const userPaidGas = balanceBefore - balanceAfter; - - console.log("RESULT C.c: Sponsored EIP-7702 tx SUCCEEDED"); - console.log(" Tx hash:", txHash); - console.log(" Block:", receipt.blockNumber); - console.log(" Gas used:", gasUsed.toString()); - console.log( - " Effective gas price (wei):", - effectiveGasPrice.toString() - ); - console.log(" Total gas cost (wei):", gasCostWei.toString()); - console.log(" User ETH change (wei):", userPaidGas.toString()); - console.log(" User paid zero gas:", userPaidGas === BigInt(0)); - console.log(" Latency (ms):", latencyMs); - - expect(userPaidGas).toBe(BigInt(0)); - expect(receipt.status).toBe("success"); - } catch (error: unknown) { - const message = error instanceof Error ? error.message : String(error); - console.log("RESULT C.c: Sponsored EIP-7702 tx FAILED"); - console.log(" Error:", message); - if (error instanceof Error && error.stack) { - console.log( - " Stack:", - error.stack.split("\n").slice(0, 5).join("\n") - ); - } - throw error; - } - }); - } -); - -// --------------------------------------------------------------------------- -// Scenario D: ERC-4337 pure fallback (standard smart account, no EIP-7702) -// Only relevant if Scenarios B+C fail. Tests that Para's signTypedData -// works with ERC-4337 UserOperation signing. -// --------------------------------------------------------------------------- -describe.skipIf(!HAS_ALL)( - "Scenario D: ERC-4337 pure fallback (no EIP-7702)", - () => { - it("D: creates and submits a sponsored UserOp via standard smart account", { - timeout: 120_000, - }, async () => { - const { createPublicClient, http } = await import("viem"); - const { privateKeyToAccount } = await import("viem/accounts"); - const { baseSepolia } = await import("viem/chains"); - const { createSmartAccountClient } = await import("permissionless"); - const { toSimpleSmartAccount } = await import("permissionless/accounts"); - const { createPimlicoClient } = await import( - "permissionless/clients/pimlico" - ); - - // Throwaway key -- testing ERC-4337 pipeline, not Para integration - const throwawayAccount = privateKeyToAccount( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as `0x${string}` - ); - - const publicClient = createPublicClient({ - chain: baseSepolia, - transport: http("https://base-sepolia-rpc.publicnode.com"), - }); - - const pimlicoUrl = `https://api.pimlico.io/v2/${CHAIN_ID}/rpc?apikey=${PIMLICO_API_KEY}`; - - const pimlicoClient = createPimlicoClient({ - transport: http(pimlicoUrl), - entryPoint: { - address: - "0x0000000071727De22E5E9d8BAf0edAc6f37da032" as `0x${string}`, - version: "0.7", - }, - }); - - const startTime = Date.now(); - - try { - const smartAccount = await toSimpleSmartAccount({ - client: publicClient, - owner: throwawayAccount, - }); - - console.log(" Smart account address:", smartAccount.address); - - // Fetch gas prices from Pimlico to satisfy paymaster validation - const gasPrices = await pimlicoClient.getUserOperationGasPrice(); - - const smartAccountClient = createSmartAccountClient({ - account: smartAccount, - chain: baseSepolia, - bundlerTransport: http(pimlicoUrl), - paymaster: pimlicoClient, - userOperation: { - estimateFeesPerGas: async () => gasPrices.fast, - }, - }); - - const txHash = await smartAccountClient.sendTransaction({ - to: smartAccount.address, - value: BigInt(0), - data: "0x" as `0x${string}`, - }); - - const receipt = await publicClient.waitForTransactionReceipt({ - hash: txHash, - }); - - const latencyMs = Date.now() - startTime; - - console.log("RESULT D: ERC-4337 fallback SUCCEEDED"); - console.log(" Tx hash:", txHash); - console.log(" Block:", receipt.blockNumber); - console.log(" Gas used:", receipt.gasUsed.toString()); - console.log(" Latency (ms):", latencyMs); - console.log(" Status:", receipt.status); - - expect(receipt.status).toBe("success"); - } catch (error: unknown) { - const message = error instanceof Error ? error.message : String(error); - console.log("RESULT D: ERC-4337 fallback FAILED"); - console.log(" Error:", message); - throw error; - } - }); - } -); diff --git a/tests/integration/user-route.test.ts b/tests/integration/user-route.test.ts index aaec29a42..1bd1614ad 100644 --- a/tests/integration/user-route.test.ts +++ b/tests/integration/user-route.test.ts @@ -9,12 +9,12 @@ const { mockGetDualAuthContext, mockUsersFindFirst, mockAccountsFindFirst, - mockGetUserWallet, + mockGetOrganizationWallet, } = vi.hoisted(() => ({ mockGetDualAuthContext: vi.fn(), mockUsersFindFirst: vi.fn(), mockAccountsFindFirst: vi.fn(), - mockGetUserWallet: vi.fn(), + mockGetOrganizationWallet: vi.fn(), })); vi.mock("@/lib/middleware/auth-helpers", () => ({ @@ -49,8 +49,8 @@ vi.mock("@/lib/db/schema", () => ({ accounts: { userId: "user_id" }, })); -vi.mock("@/lib/para/wallet-helpers", () => ({ - getUserWallet: mockGetUserWallet, +vi.mock("@/lib/web3/wallet-helpers", () => ({ + getOrganizationWallet: mockGetOrganizationWallet, })); vi.mock("@/lib/logging", () => ({ @@ -118,7 +118,7 @@ describe("GET /api/user", () => { isAnonymous: false, }); mockAccountsFindFirst.mockResolvedValue({ providerId: "credential" }); - mockGetUserWallet.mockResolvedValue({ walletAddress: "0xabc" }); + mockGetOrganizationWallet.mockResolvedValue({ walletAddress: "0xabc" }); const response = await GET(buildRequest()); expect(response.status).toBe(200); @@ -142,7 +142,7 @@ describe("GET /api/user", () => { isAnonymous: false, }); mockAccountsFindFirst.mockResolvedValue({ providerId: "credential" }); - mockGetUserWallet.mockRejectedValue(new Error("no wallet")); + mockGetOrganizationWallet.mockRejectedValue(new Error("no wallet")); const response = await GET(buildRequest()); expect(response.status).toBe(200); diff --git a/tests/integration/web3-steps.test.ts b/tests/integration/web3-steps.test.ts index 4a9f2e10a..acd7dc5e1 100644 --- a/tests/integration/web3-steps.test.ts +++ b/tests/integration/web3-steps.test.ts @@ -94,7 +94,7 @@ vi.mock("@/lib/db", () => ({ })); // Mock Para wallet helpers -vi.mock("@/lib/para/wallet-helpers", () => ({ +vi.mock("@/lib/web3/wallet-helpers", () => ({ initializeWalletSigner: vi.fn().mockResolvedValue({ getAddress: vi .fn() diff --git a/tests/unit/approve-token.test.ts b/tests/unit/approve-token.test.ts index 7c53b60e1..5f9da1b0f 100644 --- a/tests/unit/approve-token.test.ts +++ b/tests/unit/approve-token.test.ts @@ -93,7 +93,7 @@ vi.mock("@/lib/web3/resolve-org-context", () => ({ // Mock wallet helpers const mockGetWalletAddress = vi.fn(); const mockInitializeSigner = vi.fn(); -vi.mock("@/lib/para/wallet-helpers", () => ({ +vi.mock("@/lib/web3/wallet-helpers", () => ({ getOrganizationWalletAddress: (...args: unknown[]) => mockGetWalletAddress(...args), initializeWalletSigner: (...args: unknown[]) => mockInitializeSigner(...args), diff --git a/tests/unit/ensure-wallet-integration.test.ts b/tests/unit/ensure-wallet-integration.test.ts index 9e149fc43..d5f985f7b 100644 --- a/tests/unit/ensure-wallet-integration.test.ts +++ b/tests/unit/ensure-wallet-integration.test.ts @@ -5,7 +5,7 @@ vi.mock("server-only", () => ({})); const mockOrganizationHasWallet = vi.fn(); const mockGetOrganizationWallet = vi.fn(); -vi.mock("@/lib/para/wallet-helpers", () => ({ +vi.mock("@/lib/web3/wallet-helpers", () => ({ organizationHasWallet: (...args: unknown[]) => mockOrganizationHasWallet(...args), getOrganizationWallet: (...args: unknown[]) => diff --git a/tests/unit/sponsored-client.test.ts b/tests/unit/sponsored-client.test.ts index 6e12ce2b2..85fdaef5b 100644 --- a/tests/unit/sponsored-client.test.ts +++ b/tests/unit/sponsored-client.test.ts @@ -63,8 +63,8 @@ vi.mock("@/lib/metrics/types", () => ({ LabelKeys: {}, MetricNames: {}, })); -vi.mock("@/lib/para/viem-account-adapter", () => ({ - createParaViemAccount: vi.fn().mockResolvedValue({ +vi.mock("@/lib/web3/turnkey-viem-account", () => ({ + createTurnkeyViemAccount: vi.fn().mockResolvedValue({ account: { address: "0xabc" }, walletRecord: { walletAddress: "0xabc" }, }), diff --git a/tests/unit/write-contract-core.test.ts b/tests/unit/write-contract-core.test.ts index cef321a9f..9cfa7407f 100644 --- a/tests/unit/write-contract-core.test.ts +++ b/tests/unit/write-contract-core.test.ts @@ -124,7 +124,7 @@ vi.mock("@/lib/web3/resolve-org-context", () => ({ }), })); -vi.mock("@/lib/para/wallet-helpers", () => ({ +vi.mock("@/lib/web3/wallet-helpers", () => ({ getOrganizationWalletAddress: vi .fn() .mockResolvedValue("0xwalletaddress1234567890123456789012345678"), @@ -156,10 +156,10 @@ vi.mock("@/lib/web3/transaction-manager", () => ({ ), })); -// Import mocks for assertion -import { initializeWalletSigner } from "@/lib/para/wallet-helpers"; import { getChainIdFromNetwork } from "@/lib/rpc/network-utils"; import { parsePriorityFeeGwei } from "@/lib/web3/gas-defaults"; +// Import mocks for assertion +import { initializeWalletSigner } from "@/lib/web3/wallet-helpers"; // Import SUT after all mocks import { writeContractCore } from "@/plugins/web3/steps/write-contract-core"; @@ -220,7 +220,7 @@ describe("writeContractCore signer chain ID", () => { }); it("should pass resolved chainId to initializeWalletSigner", async () => { - vi.mocked(getChainIdFromNetwork).mockReturnValue(11155111); + vi.mocked(getChainIdFromNetwork).mockReturnValue(11_155_111); await writeContractCore({ contractAddress: "0x1234567890123456789012345678901234567890", @@ -233,7 +233,7 @@ describe("writeContractCore signer chain ID", () => { expect(initializeWalletSigner).toHaveBeenCalledWith( "org-1", "https://rpc.example.com", - 11155111 + 11_155_111 ); }); diff --git a/tests/unit/write-failover.test.ts b/tests/unit/write-failover.test.ts index ee72ee7c9..7248ccc34 100644 --- a/tests/unit/write-failover.test.ts +++ b/tests/unit/write-failover.test.ts @@ -38,7 +38,7 @@ vi.mock("@/lib/logging", () => ({ logUserError: vi.fn(), })); -vi.mock("@/lib/para/wallet-helpers", () => ({ +vi.mock("@/lib/web3/wallet-helpers", () => ({ initializeWalletSigner: vi.fn(), }));