import * as ed from '@noble/ed25519';
import {
ID_GATEWAY_ADDRESS,
ID_REGISTRY_ADDRESS,
ViemLocalEip712Signer,
idGatewayABI,
idRegistryABI,
NobleEd25519Signer,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
} from '@farcaster/hub-nodejs';
import { bytesToHex, createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const APP_PRIVATE_KEY = '0x00';
const ALICE_PRIVATE_KEY = '0x00';
/*******************************************************************************
* Setup - Create local accounts, Viem clients, helpers, and constants.
*******************************************************************************/
/**
* Create Viem public (read) and wallet (write) clients.
*/
const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
const walletClient = createWalletClient({
chain: optimism,
transport: http(),
});
/**
* A local account representing your app. You'll
* use this to sign key metadata and send
* transactions on behalf of users.
*/
const app = privateKeyToAccount(APP_PRIVATE_KEY);
const appAccountKey = new ViemLocalEip712Signer(app as any);
console.log('App:', app.address);
/**
* A local account representing Alice, a user.
*/
const alice = privateKeyToAccount(ALICE_PRIVATE_KEY);
const aliceAccountKey = new ViemLocalEip712Signer(alice as any);
console.log('Alice:', alice.address);
/**
* Generate a deadline timestamp one hour from now.
* All Farcaster EIP-712 signatures include a deadline, a block timestamp
* after which the signature is no longer valid.
*/
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Set the signatures' deadline to 1 hour from now
const FARCASTER_RECOVERY_PROXY = '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7';
/*******************************************************************************
* IdGateway - register - Register an app FID.
*******************************************************************************/
/**
* Get the current price to register. We're not going to register any
* extra storage, so we pass 0n as the only argument.
*/
const price = await publicClient.readContract({
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'price',
args: [0n],
});
/**
* Call `register` to register an FID to the app account.
*/
const { request } = await publicClient.simulateContract({
account: app,
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'register',
args: [FARCASTER_RECOVERY_PROXY, 0n],
value: price,
});
await walletClient.writeContract(request);
/**
* Read the app FID from the Id Registry contract.
*/
const APP_FID = await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'idOf',
args: [app.address],
});
/*******************************************************************************
* KeyGateway - addFor - Add an account key to Alice's FID.
*******************************************************************************/
/**
* To add an account key to Alice's FID, we need to follow four steps:
*
* 1. Create a new account key pair for Alice.
* 2. Use our app account to create a Signed Key Request.
* 3. Collect Alice's `Add` signature.
* 4. Call the contract to add the key onchain.
*/
/**
* 1. Create an Ed25519 account key pair for Alice and get the public key.
*/
const privateKeyBytes = ed.utils.randomPrivateKey();
const accountKey = new NobleEd25519Signer(privateKeyBytes);
let accountPubKey = new Uint8Array();
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
accountPubKey = accountKeyResult.value;
/**
* 2. Generate a Signed Key Request from the app account.
*/
const signedKeyRequestMetadata =
await appAccountKey.getSignedKeyRequestMetadata({
requestFid: APP_FID,
key: accountPubKey,
deadline,
});
if (signedKeyRequestMetadata.isOk()) {
const metadata = bytesToHex(signedKeyRequestMetadata.value);
/**
* 3. Read Alice's nonce from the Key Gateway.
*/
const aliceNonce = await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [alice.address],
});
/**
* Then, collect her `Add` signature.
*/
const aliceSignature = await aliceAccountKey.signAdd({
owner: alice.address as `0x${string}`,
keyType: 1,
key: accountPubKey,
metadataType: 1,
metadata,
nonce: aliceNonce,
deadline,
});
if (aliceSignature.isOk()) {
/**
* Call `addFor` with Alice's signature and the signed key request.
*/
const { request } = await publicClient.simulateContract({
account: app,
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'addFor',
args: [
alice.address,
1,
bytesToHex(accountPubKey),
1,
metadata,
deadline,
bytesToHex(aliceSignature.value),
],
});
await walletClient.writeContract(request);
}
}
}