Firn's Snap allows websites to securely invoke Firn Protocol on behalf of users.
An end-to-end, open-source working example exhibiting the below methods is available, and hosted, at Tome; see also firnprotocol/tome
for the source.
Connect to the Firn Snap in the standard way; that is:
await provider.request({
method: "wallet_requestSnaps",
params: { "npm:@firnprotocol/snap": {} },
});
The initialize
method prompts the user to "log into" his Firn account, on behalf of his currently logged-in Ethereum account (in practice, this entails signing a special message). As a side effect, it caches the user's secret Firn key in secure, encrypted storage (visible only within the Firn snap, and not to the calling website). If this method is called more than once, then the additional calls will be no-ops.
This method must be called before either of the below methods are. In practice, you may want to call this method immediately after prompting the user to connect the Snap in the first place.
await window.ethereum.request({
method: "wallet_invokeSnap",
params: {
snapId: "npm:@firnprotocol/snap",
request: { method: "initialize" },
},
});
This method will either return nothing (upon success) or will throw an error (upon a failure).
The requestBalance
method prompts the user to disclose his Firn balance. The RPC method will either return the user's Firn balance—denominated in milliether (!)—as a plain JavaScript number, or will throw an error. Here's an example invocation:
const balance = await window.ethereum.request({
// might throw; will be handled above
method: "wallet_invokeSnap",
params: {
snapId: "npm:@firnprotocol/snap",
request: { method: "requestBalance" },
},
});
console.log(`User's Firn balance is ${(balance / 1000).toFixed(3)} ETH.`);
The transact
method prompts the user to anonymously execute a prescribed transaction
using his Firn account. It will either return the transactionHash
of the resulting successful, mined transaction, or else will throw a descriptive error. Here's an example invocation:
import { encodeFunctionData } from "viem";
const TOME_ABI = [
{
inputs: [
{
internalType: "string",
name: "message",
type: "string",
},
],
name: "broadcast",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
];
const data = encodeFunctionData({
abi: TOME_ABI,
functionName: "broadcast",
args: ["A test message."],
});
const transaction = {
to: "0x0D9993a3e3A0e73633c153CCf49A0bD17159A60D", // Tome address on Base
data, // a bytes-like hex string
value: 0, // a plain `Number`, denominated in milli-ether
};
const transactionHash = await window.ethereum.request({
method: "wallet_invokeSnap",
params: {
snapId: defaultSnapOrigin,
request: { method: "transact", params: transaction },
},
});
console.log(`Transaction successful! Its hash is ${transactionHash}.`);
Further details and usage examples can be found at Tome.