diff --git a/.changelog/autoswap-access-key.md b/.changelog/autoswap-access-key.md new file mode 100644 index 0000000..2a2eeab --- /dev/null +++ b/.changelog/autoswap-access-key.md @@ -0,0 +1,6 @@ +--- +tempo-wallet: patch +--- + +Authorize both USDC.e and PathUSD spending for mainnet CLI access keys so paid +clients can atomically swap PathUSD into a service's requested currency. diff --git a/src/commands/identity.ts b/src/commands/identity.ts index 42cabf5..f7bf7f5 100644 --- a/src/commands/identity.ts +++ b/src/commands/identity.ts @@ -50,7 +50,7 @@ export async function loginHandler(options: { network: options.network, noBrowser: options.browser === false, }); - const result = await connect(provider); + const result = await connect(provider, options.network); return { accounts: result.accounts.map((account) => account.address), @@ -61,7 +61,7 @@ export async function loginHandler(options: { export async function refreshHandler(options: { network?: string | undefined }) { console.error(`Auth URL: ${refreshAuthUrl(options.network)}`); const provider = createProvider({ network: options.network }); - const result = await connect(provider); + const result = await connect(provider, options.network); return { accounts: result.accounts.map((account) => account.address), diff --git a/src/commands/request.ts b/src/commands/request.ts index 6f67df6..7c9e5d3 100644 --- a/src/commands/request.ts +++ b/src/commands/request.ts @@ -523,7 +523,7 @@ async function payAndRetryRequest( payment.onChallengeReceived(async ({ challenge, createCredential }) => { enforceMaxSpend(challenge, options); if (provider && providerState!.store.getState().accounts.length === 0) - await ensureProviderAccounts(provider); + await ensureProviderAccounts(provider, options.network); return await createCredential(paymentContext(challenge, options) as never); }); @@ -670,7 +670,7 @@ export async function resolvePaymentIdentity(options: RequestOptions) { const providerState = provider as unknown as { store: { getState(): { accounts: { address: string }[]; activeAccount: number } }; }; - await ensureProviderAccounts(provider); + await ensureProviderAccounts(provider, options.network); const getClient = ({ chainId }: { chainId?: number | undefined }) => { const client = provider.getClient({ chainId }); const state = providerState.store.getState(); @@ -699,10 +699,13 @@ export async function resolvePaymentIdentity(options: RequestOptions) { }; } -async function ensureProviderAccounts(provider: Parameters[0]) { +async function ensureProviderAccounts( + provider: Parameters[0], + network?: string | undefined, +) { const accounts = (await provider.request({ method: "eth_accounts" })) as unknown[]; if (accounts.length > 0) return; - await connect(provider); + await connect(provider, network); } export async function storedAccessKeyIdentity(walletState: WalletState, options: RequestOptions) { diff --git a/src/provider.ts b/src/provider.ts index 9b0f58b..255eeb1 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,7 +1,9 @@ import type { Provider as CoreProvider } from "accounts"; import { Provider, Storage } from "accounts/cli"; +import { parseUnits, toHex } from "viem"; import { openExternal } from "./shared/process.js"; +import { moderatoToken, usdcToken } from "./shared/constants.js"; export const accessKeyAuthorizationSeconds = 30 * 86_400; @@ -23,7 +25,12 @@ export function createProvider( }); } -export async function connect(provider: CoreProvider.Provider) { +export function accessKeyLimits(network: string | undefined) { + const tokens = network === "testnet" ? [moderatoToken] : [usdcToken, moderatoToken]; + return tokens.map((token) => ({ limit: toHex(parseUnits("100", 6)), token })); +} + +export async function connect(provider: CoreProvider.Provider, network?: string | undefined) { return provider.request({ method: "wallet_connect", params: [ @@ -31,6 +38,7 @@ export async function connect(provider: CoreProvider.Provider) { capabilities: { authorizeAccessKey: { expiry: Math.floor(Date.now() / 1000) + accessKeyAuthorizationSeconds, + limits: accessKeyLimits(network), }, }, }, diff --git a/test/identity.test.ts b/test/identity.test.ts index dd5284b..fb2a306 100644 --- a/test/identity.test.ts +++ b/test/identity.test.ts @@ -627,10 +627,39 @@ limit = "100000000" capabilities: { authorizeAccessKey: { expiry: Math.floor(Date.now() / 1000) + accessKeyAuthorizationSeconds, + limits: [ + { limit: "0x5f5e100", token: "0x20c000000000000000000000b9537d11c60e8b50" }, + { limit: "0x5f5e100", token: "0x20c0000000000000000000000000000000000000" }, + ], }, }, }, ], }); }); + + it("requests only PathUSD spending permission on testnet", async () => { + const request = vi.fn().mockResolvedValue({ accounts: [] }); + + await connect({ request } as never, "testnet"); + + expect(request).toHaveBeenCalledWith( + expect.objectContaining({ + params: [ + expect.objectContaining({ + capabilities: { + authorizeAccessKey: expect.objectContaining({ + limits: [ + { + limit: "0x5f5e100", + token: "0x20c0000000000000000000000000000000000000", + }, + ], + }), + }, + }), + ], + }), + ); + }); });