Skip to content

Instantly share code, notes, and snippets.

@btspoony
Created September 21, 2024 04:11
Show Gist options
  • Save btspoony/ef7167e961eb942418bd370db6f83ee0 to your computer and use it in GitHub Desktop.
Save btspoony/ef7167e961eb942418bd370db6f83ee0 to your computer and use it in GitHub Desktop.
Flow Backend Snippets
import FlowService from "./flow.service";
// Here is the configuration file for fixes.
import flowJSON from "@/flow.json" assert { type: "json" };
let _instance: FlowService;
export async function getFlowInstance(): Promise<FlowService> {
if (!_instance) {
_instance = new FlowService(flowJSON);
await _instance.onModuleInit();
}
return _instance;
}
import * as fcl from "@onflow/fcl";
import type { TransactionStatus } from '@onflow/typedefs';
export type NetworkType = "mainnet" | "testnet" | "emulator";
let isGloballyInited = false;
let globallyPromise = null;
export default class FlowService {
public readonly network: NetworkType;
private readonly flowJSON: object;
/**
* Initialize the Flow SDK
*/
constructor(flowJSON: object) {
this.network =
(import.meta.env.PUBLIC_FLOW_NETWORK as NetworkType) ?? "emulator";
this.flowJSON = flowJSON;
}
async onModuleInit() {
if (isGloballyInited) return;
const cfg = fcl.config();
// Required
await cfg.put("flow.network", this.network);
// Set the maximum of gas limit
await cfg.put("fcl.limit", 9999);
switch (this.network) {
case "mainnet":
await cfg.put(
"accessNode.api",
import.meta.env.INTERNAL_MAINNET_ENDPOINT ??
"https://mainnet.onflow.org"
);
break;
case "testnet":
await cfg.put("accessNode.api", "https://testnet.onflow.org");
break;
case "emulator":
await cfg.put("accessNode.api", "http://localhost:8888");
break;
default:
throw new Error(`Unknown network: ${String(this.network)}`);
}
// Load Flow JSON
await cfg.load({ flowJSON: this.flowJSON });
isGloballyInited = true;
}
/**
* Ensure the Flow SDK is initialized
*/
private async ensureInited() {
if (isGloballyInited) return;
if (!globallyPromise) {
globallyPromise = this.onModuleInit();
}
return await globallyPromise;
}
/**
* Get account information
*/
async getAccount(addr: string): Promise<any> {
await this.ensureInited();
return await fcl.send([fcl.getAccount(addr)]).then(fcl.decode);
}
/**
* General method of sending transaction
*/
async sendTransaction(
code: string,
args: fcl.ArgumentFunction,
mainAuthz?: fcl.FclAuthorization,
extraAuthz?: fcl.FclAuthorization[]
) {
await this.ensureInited();
if (typeof mainAuthz !== "undefined") {
return await fcl.mutate({
cadence: code,
args: args,
proposer: mainAuthz,
payer: mainAuthz,
authorizations:
(extraAuthz?.length ?? 0) === 0
? [mainAuthz]
: [mainAuthz, ...extraAuthz],
});
} else {
return await fcl.mutate({
cadence: code,
args: args,
});
}
}
/**
* Get transaction status
*/
async getTransactionStatus(
transactionId: string
): Promise<TransactionStatus> {
await this.ensureInited();
return await fcl.tx(transactionId).onceExecuted();
}
/**
* Get chain id
*/
async getChainId() {
await this.ensureInited();
return await fcl.getChainId();
}
/**
* Send transaction with single authorization
*/
async onceTransactionSealed(
transactionId: string
): Promise<TransactionStatus> {
await this.ensureInited();
return fcl.tx(transactionId).onceSealed();
}
/**
* Get block object
* @param blockId
*/
async getBlockHeaderObject(blockId: string): Promise<fcl.BlockHeaderObject> {
await this.ensureInited();
return await fcl
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
.send([fcl.getBlockHeader(), fcl.atBlockId(blockId)])
.then(fcl.decode);
}
/**
* Send script
*/
async executeScript<T>(
code: string,
args: fcl.ArgumentFunction,
defaultValue: T
): Promise<T> {
await this.ensureInited();
try {
const queryResult = await fcl.query({
cadence: code,
args,
});
return (queryResult as T) ?? defaultValue;
} catch (e) {
console.error(e);
return defaultValue;
}
}
}
import elliptic from 'elliptic';
import * as fcl from '@onflow/fcl';
import type { CompositeSignature } from '@onflow/typedefs';
import { getFlowInstance } from './flow.service.factory';
import PureSigner from './pure.signer';
const ec = new elliptic.ec('p256');
export default class FlowSigner {
constructor(
public readonly address: string,
private readonly privateKeyHex?: string
) {}
/**
* Send a transaction
* @param code Cadence code
* @param args Cadence arguments
*/
async sendTransaction(
code: string,
args: fcl.ArgumentFunction,
authz?: fcl.FclAuthorization
) {
const flowService = await getFlowInstance();
return await flowService.sendTransaction(
code,
args,
authz ?? this.buildAuthorization()
);
}
/**
* Execute a script
* @param code Cadence code
* @param args Cadence arguments
*/
async executeScript<T>(
code: string,
args: fcl.ArgumentFunction,
defaultValue: T
): Promise<T> {
const flowService = await getFlowInstance();
return await flowService.executeScript(code, args, defaultValue);
}
/**
* Build authorization
*/
buildAuthorization(accountIndex = 0, privateKey = this.privateKeyHex) {
const address = this.address;
if (!privateKey) {
throw new Error('No private key provided');
}
return (account: any) => {
return {
...account,
tempId: `${address}-${accountIndex}`,
addr: fcl.sansPrefix(address),
keyId: Number(accountIndex),
signingFunction: (signable: any): Promise<CompositeSignature> => {
return Promise.resolve({
f_type: 'CompositeSignature',
f_vsn: '1.0.0',
addr: fcl.withPrefix(address),
keyId: Number(accountIndex),
signature: this.signMessage(signable.message, privateKey),
});
},
};
};
}
/**
* Sign a message
* @param message Message to sign
*/
signMessage(message: string, privateKey = this.privateKeyHex) {
return PureSigner.signWithKey(privateKey, message);
}
}
import elliptic from "elliptic";
import { SHA3 } from "sha3";
export default class PureSigner {
/**
* Sign a message with a private key
*/
static signWithKey(privateKeyHex: string, msg: string) {
const ec = new elliptic.ec("p256");
const key = ec.keyFromPrivate(Buffer.from(privateKeyHex, "hex"));
const sig = key.sign(this._hashMsg(msg));
const n = 32;
const r = sig.r.toArrayLike(Buffer, "be", n);
const s = sig.s.toArrayLike(Buffer, "be", n);
return Buffer.concat([r.valueOf(), s.valueOf()]).toString('hex');
}
/**
* Hash a message
*/
private static _hashMsg(msg: string) {
const sha = new SHA3(256);
sha.update(Buffer.from(msg, "hex"));
return sha.digest();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment