npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@megaeth-labs/wallet-intent

v1.0.1

Published

Shared V0.6 Intent primitives for MegaETH wallet, merchant, and SDKs.

Readme

@megaeth-labs/wallet-intent

Shared primitives for building, signing, and submitting the MegaETH V0.6 Intent payload that the relay's wallet_sendCalls consumes.

Used by:

  • @megaeth-labs/wallet — calls sendCalls directly for non-sponsored sends, and points it at a merchant URL for sponsored sends.
  • @megaeth-labs/wallet-merchant — the sponsor backend receives the unsigned intent and signing keyType over HTTP, sets payer = merchant address, fills paymentSignature, re-runs prepareIntent with that keyType, and returns the re-derived { intent, digest } for the wallet to sign.

The package exposes a tiny surface: a single high-level sendCalls action that wraps orchestrator lookups, nonce reads, payment-rate fetches, the optional merchant round-trip, signing, and the JSON-RPC envelope; a sibling upgradeAccount action that builds the EIP-7702 + multichain preCall envelope; signMessage / signTypedData / verifyMessage for personal-sign and typed-data flows (with ERC-8010 wrapping when the EOA is not yet delegated); and the low-level prepareIntent / IntentV06 / getPaymentPerGas / pickPaymentPerGasFor / ESTIMATED_GAS_BUDGET building blocks for callers that need them.

paymentPerGas is quoted by the relay as a 1e18 fixed-point scalar — the orchestrator computes the actual fee as gasUsed × paymentPerGas / 10^18, so multiplying gas by the raw paymentPerGas overshoots by 1e18. Divide by PAYMENT_PER_GAS_FIXED_POINT_SCALAR to land at the correct fee-token base-unit amount, regardless of the token's decimals.

Install

pnpm add @megaeth-labs/wallet-intent

During local development the consumer repos use a file: dependency:

{
    "dependencies": {
        "@megaeth-labs/wallet-intent": "*"
    }
}

Run pnpm build in this repo first so dist/ exists.

Surface

import {
    sendCalls,
    prepareIntent,
    getPaymentPerGas,
    pickPaymentPerGasFor,
    ESTIMATED_GAS_BUDGET,
    erc1271SignPayload,
    executeAs,
    findSubAccountsByLogs,
    keyHashForParent,
    PAYMENT_PER_GAS_FIXED_POINT_SCALAR,
    PAYMENT_PER_GAS_PRECISION,
    upgradeAccount,
    verifySubAccounts,
    getKeys,
    signMessage,
    signTypedData,
    verifyMessage,
    type IntentSigningKeyType,
    type IntentV06,
    type UpgradeAccount,
} from '@megaeth-labs/wallet-intent';

Most callers only need sendCalls(client, input). Pass merchantUrl to route the unsigned intent through a sponsor; omit it for self-paid sends. When paymentMaxAmount is omitted, sendCalls derives a raw token cap as ceil((paymentGasBudget ?? ESTIMATED_GAS_BUDGET) × paymentPerGas / 1e18). Pass an explicit paymentMaxAmount only when you want to override that computed cap.

When the supplied signing key is the EOA's own root signer (a secp256k1 key whose publicKey matches account.address), sendCalls signs with a raw 65-byte secp256k1 signature; for sub-keys (other secp256k1, p256, webauthn) it produces a wrapped signature so the orchestrator can recover the matching keyHash from keyStorage.

Sub-accounts (address keys)

An address-type key authorizes another account to act on this one. It holds no private key of its own — the signature it stands for is an ERC-1271 signature produced by the account named in its publicKey. Pass it as key along with an externalAdminKey that account can actually sign with:

const mainAccount = Key.from({
    type: 'address',
    publicKey: MAIN_ACCOUNT_ADDRESS,
    role: 'admin',
});

// Authorize the main account as the sub-account's only admin.
const upgrade = await upgradeAccount(client, {
    account: subEoa,
    authorizeKeys: [mainAccount],
});

// ...then send from the sub-account, signed by the main account's passkey.
await sendCalls(client, {
    account: Account.from({ address: subEoa.address, keys: [mainAccount] }),
    key: mainAccount,
    externalAdminKey: passkey,
    calls,
    authorization: upgrade,
});

sendCalls nests the two signatures: the passkey signs the payload the main account reconstructs on isValidSignature, and that blob is wrapped again with the address key's keyHash so the sub-account routes the check to the main account. The passkey is never added to the sub-account, so recovery and rotation stay a property of the main account alone.

The externalAdminKey must be a super admin on that account, or hold an explicit setSignatureCheckerApproval for this EOA — isValidSignature refuses non-admin keys for third-party callers.

Paying for a sub-account

A sub-account need not hold gas. Set payer to the account footing the bill and payerKey to a key on it; sendCalls signs the PaymentAuthorization locally, the same one a merchant backend would sign:

await sendCalls(client, {
    account: Account.from({ address: subEoa.address, keys: [mainAccount] }),
    key: mainAccount,
    externalAdminKey: passkey,
    payer: MAIN_ACCOUNT_ADDRESS,
    payerKey: passkey,
    calls,
});

Both signatures come from the same passkey here, but they are different things: the intent signature is nested through the main account's ERC-1271, while the payment signature is signed plainly, because the passkey is a key on the payer's own account and needs no indirection to reach it.

payer without payerKey or merchantUrl is rejected — the orchestrator charges a third-party payer only against a paymentSignature, so such an intent would revert on-chain.

Signing in as a sub-account (SIWE)

signMessage and signTypedData take externalAdminKey too, so a sub-account can sign an EIP-4361 message with the main account's passkey:

const signature = await signMessage(client, {
    account: Account.from({ address: subEoa.address, keys: [mainAccount] }),
    key: mainAccount,
    externalAdminKey: passkey,
    message: siweMessage,
});

// Verifies against the sub-account's address, as any SIWE backend would.
await verifyMessage(publicClient, { address: subEoa.address, message: siweMessage, signature });

This signature nests one layer deeper than an intent's. A verifier calls isValidSignature on the sub-account, which rehashes the message under its own domain before delegating to the main account — whereas the orchestrator calls unwrapAndValidateSignature directly and skips that layer. Both paths go through signAsExternalAdmin, so callers do not have to track the difference.

A sub-account can sign in before it exists on-chain, too — pass the authorization from upgradeAccount and the signature comes back wrapped in an ERC-8010 envelope:

const upgrade = await upgradeAccount(client, {
    account: subEoa,
    authorizeKeys: [mainAccount],
});

const signature = await signMessage(client, {
    account: Account.from({ address: subEoa.address, keys: [mainAccount] }),
    key: mainAccount,
    externalAdminKey: passkey,
    message: siweMessage,
    authorization: upgrade,
});

The verifier replays the 7702 authorization and executePreCalls in a simulated call before checking the signature, so the account stays undelegated and nothing is spent. Once it is delegated the envelope is dropped automatically, so callers may pass authorization unconditionally.

erc1271SignPayload(...) builds that inner payload if you need it directly (to pre-check a signature with an eth_call, say). Reach for it rather than Key.sign(key, { address }): the latter builds a verifying-contract-only payload, matching porto's account rather than the one deployed here, and signatures made that way come back 0xffffffff.

Spending a balance split across accounts

An intent has one eoa, and every call in it runs as that account — so a single intent cannot move funds from two accounts by itself. executeAs bridges that: it builds a call that makes another account act, authorized by an address key rather than by msg.sender.

// The sub-account holds 80, the main account 20, and the user wants to send 100.
const subSends = await executeAs(publicClient, {
    account: subEoa.address,
    key: mainAccount,        // the address key registered on the sub-account
    adminKey: passkey,       // a super-admin key on the main account
    calls: [transfer(recipient, 100n)],
});

await sendCalls(client, {
    account: mainAccount,
    key: passkey,
    calls: [transfer(subEoa.address, 20n), subSends],
});

Both calls sit in one executionData, so either both happen or neither does, and the recipient sees a single transfer of 100 from the sub-account. Note this is not the orchestrator's batch execute(bytes[]), which collects per-intent errors instead of reverting and would let the top-up land while the payment failed.

Two signatures are produced — the intent's and the inner opData — so with a real passkey this prompts twice. See the note on session keys above.

Finding a parent's sub-accounts

An account can list its own keys, but no account can be asked which accounts have authorized it. The wallet API is the primary index for that direction; on-chain, the fallback is a log scan:

// No deployment needed, but depends on eth_getLogs behaviour.
const found = await findSubAccountsByLogs(client, { parent });

// Or verify a list the API already gave you — one eth_call per address.
const checked = await verifySubAccounts(client, { parent, addresses });

Both verify each candidate against the account's live key state before returning it, because a historical log does not prove the relationship still holds — a sub-account can revoke its parent without telling anyone. Pass activeOnly: false to see stale entries with active: false.

Note what the log scan can and cannot tell you: the key hash is the same for an address key and a secp256k1 key on the same address, so it finds every account that authorized the parent in any form, and anyone may authorize any address. Treat its results as candidates to reconcile against the API's list, not as a user's account list.

Upgrading an EOA (EIP-7702)

upgradeAccount(client, { account, authorizeKeys }) is pure: it signs the EIP-7702 authorization for the orchestrator's account proxy and builds the multichain authorize(key) preCall, then returns

{
    account: Address;
    status: 'pending';
    authorization: SignedAuthorization;
    encodedPreCall: Hex;
}

Persist that blob yourself (e.g. in localStorage or your wallet's state) until the chain has consumed it. Pass it back as the authorization field on sendCalls, signMessage, and signTypedData; each call checks whether the EOA is already delegated on the target chain and:

  • attaches the 7702 authorization + encodedPreCall to wallet_sendCalls (and surfaces delegated: true in the receipt) when the EOA is still pending, or
  • wraps the user signature in an ERC-8010 envelope on signMessage / signTypedData so a verifier can lazily run executePreCalls before checking the signature.

When the chain is already delegated, the supplied authorization is ignored — callers may pass it unconditionally and advance their own "delegated chains" tracking off the receipt's delegated flag.

const upgrade = await upgradeAccount(client, {
    account,
    authorizeKeys: [passkey, sessionKey],
});

await sendCalls(client, { account, calls, authorization: upgrade });