accounts
Version:
Tempo Accounts SDK
101 lines • 4.79 kB
JavaScript
import { spawn } from 'node:child_process';
import { KeyAuthorization } from 'ox/tempo';
import * as z from 'zod/mini';
import { deviceCode } from '../core/adapters/deviceCode/deviceCode.js';
import * as Keystore from '../core/Keystore.js';
import * as Rpc from '../core/zod/rpc.js';
/**
* Creates a CLI bootstrap adapter backed by the Wata device-code transport
* (OAuth 2.0 Device Authorization Grant, RFC 8628 with PKCE).
*
* Authorization opens the wallet's verification page in a browser; the wallet
* responds with a signed key authorization once the user approves.
*/
export function cli(options) {
const { host, name = 'Tempo CLI', open = defaultOpen, pollingInterval, rdns = 'xyz.tempo.cli', timeout, } = options;
return deviceCode({
actions: ({ getClient, request, store }) => {
return {
// TODO: Move pending access-key replacement into the shared provider once
// all remote adapters support the extended request and response.
// Limit updates run their own ceremony: the wallet returns a
// replacement signed authorization for a pending (unpublished) key,
// or applies the update on-chain for a published one.
async updateAccessKey(parameters) {
const chainId = Number(parameters.chainId ?? store.getState().chainId);
const current = store.accessKeys.list({
accessKey: parameters.accessKeyAddress,
account: parameters.address,
chainId,
})[0];
const status = current?.keyAuthorization
? await store.accessKeys.getStatus({
accessKey: parameters.accessKeyAddress,
account: parameters.address,
chainId,
client: getClient({ chainId }),
})
: undefined;
const pendingKeyAuthorization = status === 'pending' && current?.keyAuthorization
? KeyAuthorization.toRpc(current.keyAuthorization)
: undefined;
const encoded = z.encode(Rpc.wallet_updateAccessKey.parameters, {
...parameters,
chainId: BigInt(chainId),
});
const result = (await request({
context: { account: parameters.address, chainId },
method: 'wallet_updateAccessKey',
params: [
{
...encoded,
...(pendingKeyAuthorization ? { keyAuthorization: pendingKeyAuthorization } : {}),
},
],
}));
if (result?.keyAuthorization)
store.accessKeys.updateAuthorization({
accessKey: parameters.accessKeyAddress,
account: parameters.address,
authorization: KeyAuthorization.fromRpc(result.keyAuthorization),
chainId,
});
},
};
},
// The CLI persists to string-based filesystem storage, so its p256
// default opts into an extractable WebCrypto key (a non-extractable one
// could not survive a restart). secp256k1 stays available for explicit
// requests.
keystores: {
p256: Keystore.webCryptoP256({ extractable: true }),
secp256k1: Keystore.secp256k1(),
},
meta: { name },
name,
async onPrompt(prompt) {
if (!prompt.verificationUriFull) {
const code = prompt.userCode.replace(/^(.{4})(.{4})$/, '$1-$2');
process.stdout.write(`Enter code ${code} at ${prompt.verificationUri}\n`);
}
await open(prompt.verificationUriFull ?? prompt.verificationUri, prompt);
},
rdns,
...(pollingInterval !== undefined ? { pollingInterval } : {}),
...(timeout !== undefined ? { timeout } : {}),
url: host,
});
}
function defaultOpen(url) {
const command = process.platform === 'darwin'
? { command: 'open', args: [url] }
: process.platform === 'win32'
? { command: 'cmd', args: ['/c', 'start', '', url] }
: { command: 'xdg-open', args: [url] };
const child = spawn(command.command, command.args, {
detached: true,
stdio: 'ignore',
});
child.unref();
}
//# sourceMappingURL=adapter.js.map