Skip to content

Commit

Permalink
migrated all utils
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquinsoza committed Dec 5, 2024
1 parent 9111414 commit 97f0676
Show file tree
Hide file tree
Showing 19 changed files with 745 additions and 712 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
],
"devDependencies": {
"@size-limit/preset-small-lib": "^11.1.6",
"@types/node": "^22.10.1",
"husky": "^9.1.7",
"rollup-plugin-copy": "^3.5.0",
"size-limit": "^11.1.6",
"tsdx": "^0.14.1",
"tslib": "^2.8.1",
Expand Down
2 changes: 1 addition & 1 deletion src/networks.ts → src/config/defaultNetworks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Networks } from "@stellar/stellar-sdk";
import { StellarNetworkConfig } from "./sorobanToolkit";
import { StellarNetworkConfig } from "./toolkit";

export const testnet: StellarNetworkConfig = {
network: "testnet",
Expand Down
32 changes: 24 additions & 8 deletions src/initializeToolkit.ts → src/config/loader.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { futurenet, testnet } from "./networks";
import { StellarNetworkConfig, SorobanToolkit } from "./sorobanToolkit";
import { futurenet, testnet } from "./defaultNetworks";
import { StellarNetworkConfig, SorobanToolkit } from "./toolkit";

interface Toolkit {
getNetworkToolkit: (networkName: string) => SorobanToolkit;
listAvailableNetworks: () => string[];
}

export function initializeToolkit(
adminSecret: string,
contractPaths: Record<string, string> = {},
customNetworks?: StellarNetworkConfig[]
): Toolkit {
interface CreateToolkitOptions {
adminSecret: string;
contractPaths?: Record<string, string>;
customNetworks?: StellarNetworkConfig[];
addressBookPath?: string;
verbose?: "none" | "some" | "full";
}

export function createToolkit({
adminSecret,
contractPaths,
customNetworks,
addressBookPath = "./.soroban",
verbose = "none"
}: CreateToolkitOptions): Toolkit {
// Default networks
const defaultNetworks: Record<string, StellarNetworkConfig> = {
testnet,
Expand All @@ -37,7 +47,13 @@ export function initializeToolkit(
if (!network) {
throw new Error(`Unknown network: ${networkName}`);
}
return new SorobanToolkit({ adminSecret, network, contractPaths });
return new SorobanToolkit({
adminSecret,
network,
contractPaths,
addressBookPath,
verbose
});
},

/**
Expand Down
38 changes: 37 additions & 1 deletion src/sorobanToolkit.ts → src/config/toolkit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Horizon, Keypair, rpc } from "@stellar/stellar-sdk";
import { AddressBook } from "../utils/addressBook";

export interface StellarNetworkConfig {
network: string;
Expand All @@ -12,6 +13,8 @@ interface ToolkitOptions {
adminSecret: string;
network: StellarNetworkConfig;
contractPaths?: Record<string, string>;
addressBookPath?: string;
verbose?: "none" | "some" | "full";
}

export class SorobanToolkit {
Expand All @@ -21,9 +24,17 @@ export class SorobanToolkit {
friendbotUrl?: string;
admin: Keypair;
contractPaths: Record<string, string>;
addressBook: AddressBook;
private verbose: "none" | "some" | "full";

constructor(options: ToolkitOptions) {
const { adminSecret, network, contractPaths = {} } = options;
const {
adminSecret,
network,
contractPaths = {},
addressBookPath = "./.soroban",
verbose = "none",
} = options;

if (!adminSecret) {
throw new Error("Admin secret key is required.");
Expand All @@ -37,6 +48,8 @@ export class SorobanToolkit {
this.friendbotUrl = network.friendbotUrl;
this.admin = Keypair.fromSecret(adminSecret);
this.contractPaths = contractPaths;
this.addressBook = AddressBook.loadFromFile(network.network, addressBookPath);
this.verbose = verbose;
}

/**
Expand All @@ -60,4 +73,27 @@ export class SorobanToolkit {
createKeypair(privateKey: string): Keypair {
return Keypair.fromSecret(privateKey);
}

/**
* Log messages based on verbosity level.
* @param level - The level of verbosity for this message ("some" or "full").
* @param messages - The messages to log.
*/
private log(level: "some" | "full", ...messages: any[]): void {
if (this.verbose === "none") return;
if (this.verbose === "some" && level === "some") {
console.log(...messages);
} else if (this.verbose === "full") {
console.log(...messages);
}
}

/**
* Public logging method (to be used by managers/utilities).
* @param level - The level of verbosity for this message.
* @param messages - The messages to log.
*/
public logVerbose(level: "some" | "full", ...messages: any[]): void {
this.log(level, ...messages);
}
}
15 changes: 12 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
export * from "./initializeToolkit";
export * from "./networks";
export * from "./sorobanToolkit";
// Config
export * from "./config/loader";
export * from "./config/defaultNetworks";
export * from "./config/toolkit";

// Utils
export * from "./utils/accountUtils";

// Managers
export * from "./managers/contract";
export * from "./managers/transaction";
export * from "./managers/token";
192 changes: 192 additions & 0 deletions src/managers/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import {
Address,
Contract,
Keypair,
Operation,
StrKey,
hash,
xdr,
} from "@stellar/stellar-sdk";
import { SorobanToolkit } from "../config/toolkit";
import { resolvePath } from "../utils/utils";
import { createTransaction, createTransactionBuilder, sendTransaction } from "./transaction";
import { randomBytes } from "crypto";

export async function installContract(
toolkit: SorobanToolkit,
contractKey: string,
customBuffer?: Buffer,
source?: Keypair
) {
let contractWasm, wasmHash;

if(!customBuffer) {
const wasmPath = toolkit.getContractPath(contractKey);
contractWasm = resolvePath(wasmPath);
wasmHash = hash(contractWasm);
} else {
wasmHash = hash(customBuffer);
contractWasm = customBuffer;
}

toolkit.logVerbose("full", `Installing contract: ${contractKey}`);
toolkit.logVerbose("full", `WASM hash: ${wasmHash.toString("hex")}`);

toolkit.addressBook.setWasmHash(contractKey, wasmHash.toString("hex"));
toolkit.addressBook.writeToFile();

const op = Operation.invokeHostFunction({
func: xdr.HostFunction.hostFunctionTypeUploadContractWasm(contractWasm),
auth: [],
});

await createTransaction(toolkit, op, false, source);
}

export async function deployContract(
toolkit: SorobanToolkit,
contractKey: string,
args: xdr.ScVal[],
source?: Keypair,
) {
const contractIdSalt = randomBytes(32);
const networkId = hash(Buffer.from(toolkit.passphrase));

const contractIdPreimage = xdr.ContractIdPreimage.contractIdPreimageFromAddress(
new xdr.ContractIdPreimageFromAddress({
address: Address.fromString(source?.publicKey() ?? toolkit.admin.publicKey()).toScAddress(),
salt: contractIdSalt,
})
);

const hashIdPreimage = xdr.HashIdPreimage.envelopeTypeContractId(
new xdr.HashIdPreimageContractId({
networkId,
contractIdPreimage,
})
);

const contractId = StrKey.encodeContract(hash(hashIdPreimage.toXDR()));
toolkit.addressBook.setContractId(contractKey, contractId);
toolkit.addressBook.writeToFile();

const wasmHash = Buffer.from(toolkit.addressBook.getWasmHash(contractKey), "hex");

toolkit.logVerbose("some", `Deploying contract: ${contractKey}`);
const deployOp = Operation.invokeHostFunction({
func: xdr.HostFunction.hostFunctionTypeCreateContractV2(
new xdr.CreateContractArgsV2({
contractIdPreimage,
executable: xdr.ContractExecutable.contractExecutableWasm(wasmHash),
constructorArgs: args,
})
),
auth: [],
});

await createTransaction(toolkit, deployOp, false, source);

return contractId;
}

export async function invokeContract(
toolkit: SorobanToolkit,
contractKey: string,
method: string,
params: xdr.ScVal[],
simulate: boolean = false,
source?: Keypair,
) {
const contractId = toolkit.addressBook.getContractId(contractKey);
const contract = new Contract(contractId);

const operation = contract.call(method, ...params);

toolkit.logVerbose("some", `Invoking contract ${contractKey}: ${method}`);
return await createTransaction(toolkit, operation, simulate, source);
}

export async function invokeCustomContract(
toolkit: SorobanToolkit,
contractId: string,
method: string,
params: xdr.ScVal[],
simulate: boolean = false,
source?: Keypair,
) {
const contract = new Contract(contractId);

const operation = contract.call(method, ...params);

toolkit.logVerbose("some", `Invoking contract ${contractId}: ${method}`);
return await createTransaction(toolkit, operation, simulate, source);
}

export async function bumpContractInstance(
toolkit: SorobanToolkit,
contractId: string,
source?: Keypair
) {
const address = Address.fromString(contractId);
toolkit.logVerbose("some", `Bumping contract instance: ${contractId}`);
const contractInstanceXDR = xdr.LedgerKey.contractData(
new xdr.LedgerKeyContractData({
contract: address.toScAddress(),
key: xdr.ScVal.scvLedgerKeyContractInstance(),
durability: xdr.ContractDataDurability.persistent(),
})
);
const bumpTransactionData = new xdr.SorobanTransactionData({
resources: new xdr.SorobanResources({
footprint: new xdr.LedgerFootprint({
readOnly: [contractInstanceXDR],
readWrite: [],
}),
instructions: 0,
readBytes: 0,
writeBytes: 0,
}),
resourceFee: xdr.Int64.fromString("0"),
// @ts-ignore
ext: new xdr.ExtensionPoint(0),
});

const txBuilder = await createTransactionBuilder(toolkit, source);
txBuilder.addOperation(Operation.extendFootprintTtl({ extendTo: 535670 })); // 1 year
txBuilder.setSorobanData(bumpTransactionData);
const result = await sendTransaction(toolkit, txBuilder.build(), false, source);
return result;
}

export async function bumpContractCode(
toolkit: SorobanToolkit,
wasmHash: string,
source?: Keypair
) {
const wasmHashBuffer = Buffer.from(wasmHash, "hex");
const contractCodeXDR = xdr.LedgerKey.contractCode(
new xdr.LedgerKeyContractCode({
hash: wasmHashBuffer,
})
);
const bumpTransactionData = new xdr.SorobanTransactionData({
resources: new xdr.SorobanResources({
footprint: new xdr.LedgerFootprint({
readOnly: [contractCodeXDR],
readWrite: [],
}),
instructions: 0,
readBytes: 0,
writeBytes: 0,
}),
resourceFee: xdr.Int64.fromString("0"),
// @ts-ignore
ext: new xdr.ExtensionPoint(0),
});

const txBuilder = await createTransactionBuilder(toolkit, source);
txBuilder.addOperation(Operation.extendFootprintTtl({ extendTo: 535670 })); // 1 year
txBuilder.setSorobanData(bumpTransactionData);
const result = await sendTransaction(toolkit, txBuilder.build(), false, source);
return result;
}
43 changes: 43 additions & 0 deletions src/managers/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Address, Keypair, nativeToScVal, xdr } from "@stellar/stellar-sdk";
import { resolveInternalPath } from "../utils/utils";
import { SorobanToolkit } from "../config/toolkit";
import { deployContract, installContract } from "./contract";

/**
* Deploy the Soroban Token Contract.
* @param toolkit - The SorobanToolkit instance.
* @param source - The Keypair to use as the source for the transaction.
* @returns The WASM hash of the deployed token contract.
*/
export async function deploySorobanToken(
toolkit: SorobanToolkit,
name: string,
symbol: string,
decimals: number,
source?: Keypair
) {
toolkit.logVerbose("some", `Deploying Token: ${name} ${symbol}`);
const wasmKey = "soroban_token";
const wasmBuffer = resolveInternalPath("./soroban_token.wasm");

// Check if the WASM hash is already stored in the AddressBook
try {
const existingWasmHash = toolkit.addressBook.getWasmHash(wasmKey);
if (existingWasmHash) {
toolkit.logVerbose("full", `WASM is already installed`);
}
} catch {
// WASM not found in AddressBook, proceed with installation
toolkit.logVerbose("full", "WASM not found in AddressBook, proceeding with installation");
await installContract(toolkit, wasmKey, wasmBuffer, source);
}

const args: xdr.ScVal[] = [
new Address(source?.publicKey() ?? toolkit.admin.publicKey()).toScVal(),
nativeToScVal(decimals, {type: "u32"}),
nativeToScVal(name, {type: "string"}),
nativeToScVal(symbol, {type: "string"}),
];

return await deployContract(toolkit, wasmKey, args, source);
}
Loading

0 comments on commit 97f0676

Please sign in to comment.