Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changelog/accounts-store-owner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
wallet-cli: patch
---

Delegated CLI wallet persistence and access-key selection to the Accounts SDK while retaining legacy `keys.toml` migration.
12 changes: 6 additions & 6 deletions src/commands/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export async function currentWhoamiOutput(options: {
network?: string | undefined;
}) {
const token =
options.accessKeys[0]?.limits[0]?.token ??
options.accessKeys[0]?.limits?.[0]?.token ??
tokenAddress(options.chain ?? chainId(options.network));
const balance = await tokenBalance({
token,
Expand Down Expand Up @@ -288,7 +288,7 @@ export async function currentKeysOutput(options: {
const balances = new Map<string, TokenBalance | null>();
const keys = [];
for (const key of options.accessKeys) {
const token = key.limits[0]?.token ?? tokenAddress(key.chainId);
const token = key.limits?.[0]?.token ?? tokenAddress(key.chainId);
const balance = balances.has(token.toLowerCase())
? (balances.get(token.toLowerCase()) ?? null)
: await tokenBalance({
Expand Down Expand Up @@ -321,7 +321,7 @@ function currentKeyOutput(options: {
status: string | null;
}) {
if (!options.key) return null;
const limit = options.key.limits[0];
const limit = options.key.limits?.[0];
const token = limit?.token ?? tokenAddress(options.chain ?? options.key.chainId);
const spendingLimits = accessKeyLimitsOutput(options.key);
return {
Expand Down Expand Up @@ -350,7 +350,7 @@ function currentKeyOutput(options: {
}

function accessKeyLimitsOutput(key: WalletState["accessKeys"][number]) {
return key.limits.map((limit) => ({
return (key.limits ?? []).map((limit) => ({
unlimited: false,
symbol: tokenSymbol(limit.token),
token: limit.token.toLowerCase(),
Expand All @@ -365,11 +365,11 @@ function accessKeyScopesOutput(key: WalletState["accessKeys"][number]) {
return accessKeyScopes(key).map((scope) => ({
address: scope.address.toLowerCase(),
selector: scope.selector ?? null,
recipients: scope.recipients.map((recipient) => recipient.toLowerCase()),
recipients: (scope.recipients ?? []).map((recipient) => recipient.toLowerCase()),
}));
}

function accessKeyScopes(key: WalletState["accessKeys"][number]) {
function accessKeyScopes(key: WalletState["accessKeys"][number]): readonly AccessKeyScope[] {
if (key.scopes !== undefined) return key.scopes;
return parseKeyAuthorizationScopes(key.keyAuthorization);
}
Expand Down
104 changes: 39 additions & 65 deletions src/commands/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { setTimeout as sleep } from "node:timers/promises";

import { Challenge, Credential, PaymentRequest } from "mppx";
import { Mppx, session as tempoSession, tempo } from "mppx/client";
import { Keystore } from "accounts";
import { Store } from "accounts";
import { Session as TempoSession } from "mppx/tempo";
import {
Agent,
Expand All @@ -26,7 +26,6 @@ import {
Actions,
Chain,
Channel as TempoChannel,
KeyAuthorizationManager,
} from "viem/tempo";

import { withSessionLock } from "../payment/session-lock.js";
Expand All @@ -51,7 +50,6 @@ import {
rpcUrl,
} from "../shared/network.js";
import { getRecord, nowSeconds, parseOnChainBigInt, stringValue } from "../shared/utils.js";
import { loadWalletState, type WalletState } from "../wallet/store.js";

export type RequestOptions = {
bearer?: string | undefined;
Expand Down Expand Up @@ -663,13 +661,14 @@ export async function resolvePaymentIdentity(options: RequestOptions) {
};
}

const storedIdentity = await storedAccessKeyIdentity(await loadWalletState(), options);
if (storedIdentity) return storedIdentity;

const provider = createProvider({ network: options.network });
const providerState = provider as unknown as {
store: { getState(): { accounts: { address: string }[]; activeAccount: number } };
store: StoredAccessKeyStore;
};
await Store.waitForHydration(providerState.store as never);
const storedIdentity = await storedAccessKeyIdentity(providerState.store, options);
if (storedIdentity) return storedIdentity;

await ensureProviderAccounts(provider);
const getClient = ({ chainId }: { chainId?: number | undefined }) => {
const client = provider.getClient({ chainId });
Expand Down Expand Up @@ -705,69 +704,44 @@ async function ensureProviderAccounts(provider: Parameters<typeof connect>[0]) {
await connect(provider);
}

export async function storedAccessKeyIdentity(walletState: WalletState, options: RequestOptions) {
const activeAccount = walletState.accounts[walletState.activeAccount ?? 0];
type StoredAccessKeyStore = {
accessKeys: {
select(options: {
account: `0x${string}`;
chainId: number;
}): Promise<TempoAccount.AccessKeyAccount | undefined>;
};
getState(): {
accounts: readonly { address: string }[];
activeAccount: number;
};
};

async function storedAccessKeyIdentity(store: StoredAccessKeyStore, options: RequestOptions) {
const state = store.getState();
const activeAccount = state.accounts[state.activeAccount];
if (!activeAccount) return undefined;

const expectedChain = chainId(options.network);
for (const key of walletState.accessKeys) {
if (key.chainId !== expectedChain) continue;
if (key.keyType && key.keyType !== "secp256k1" && key.keyType !== "p256") continue;

const keyAuthorizationManager = KeyAuthorizationManager.memory();
if (key.keyAuthorization) {
await keyAuthorizationManager.set(
{
address: activeAccount.address as `0x${string}`,
accessKey: key.address as `0x${string}`,
chainId: expectedChain,
},
key.keyAuthorization as never,
);
}

const account = key.privateKey
? key.keyType === "p256"
? TempoAccount.fromP256(key.privateKey as `0x${string}`, {
access: activeAccount.address as `0x${string}`,
keyAuthorizationManager,
})
: TempoAccount.fromSecp256k1(key.privateKey as `0x${string}`, {
access: activeAccount.address as `0x${string}`,
keyAuthorizationManager,
})
: key.keyType === "p256" && key.handle && key.publicKey
? await Keystore.webCryptoP256({ extractable: true }).toAccount(
{
handle: key.handle as Keystore.Handle,
keyType: key.keyType,
publicKey: key.publicKey as `0x${string}`,
},
{
access: activeAccount.address as `0x${string}`,
keyAuthorizationManager,
},
)
: undefined;
if (!account) continue;
if (key.address.toLowerCase() !== account.accessKeyAddress.toLowerCase()) continue;
const account = await store.accessKeys.select({
account: activeAccount.address as `0x${string}`,
chainId: expectedChain,
});
if (!account) return undefined;

const getClient = ({ chainId }: { chainId?: number | undefined }) =>
createWalletClient({
account,
chain: chainId === 42431 ? Chain.tempoModerato : Chain.tempo,
transport: http(rpcUrl(chainId === 42431 ? "testnet" : "mainnet")),
});
return {
const getClient = ({ chainId }: { chainId?: number | undefined }) =>
createWalletClient({
account,
address: account.address,
getClient,
methodOptions: { account, getClient, mode: "pull" as const },
signerAddress: account.accessKeyAddress,
};
}

return undefined;
chain: chainId === 42431 ? Chain.tempoModerato : Chain.tempo,
transport: http(rpcUrl(chainId === 42431 ? "testnet" : "mainnet")),
});
return {
account,
address: account.address,
getClient,
methodOptions: { account, getClient, mode: "pull" as const },
signerAddress: account.accessKeyAddress,
};
}

type PaymentIdentity = Awaited<ReturnType<typeof resolvePaymentIdentity>>;
Expand Down
4 changes: 2 additions & 2 deletions src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export function stringValue(value: unknown) {
return typeof value === "string" ? value : "";
}

export function cleanStoredScalar(value: string) {
return value.replace(/#__bigint$/, "");
export function cleanStoredScalar(value: bigint | string) {
return value.toString().replace(/#__bigint$/, "");
}

export function sleep(ms: number) {
Expand Down
Loading