mirror of
https://github.com/alexgo-io/clarity-codegen.git
synced 2026-01-12 14:34:34 +08:00
feat: add composeTxOptions, executeReadonlyCall
This commit is contained in:
89
README.md
89
README.md
@@ -45,32 +45,75 @@ import {
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import { ParameterObjOfDescriptor, processContractCall } from "clarity-codegen";
|
||||
import { callReadOnlyFunction } from "@stacks/transactions";
|
||||
import { tupleT, stringT } from "clarity-codegen";
|
||||
import { AlexContracts } from "./generated/contracts_Alex";
|
||||
import { Operation } from "./Operation";
|
||||
|
||||
export type Contracts = typeof AlexContracts;
|
||||
export type ContractName = keyof Contracts;
|
||||
|
||||
export const callPublic = <
|
||||
T extends ContractName,
|
||||
F extends keyof Contracts[T],
|
||||
Descriptor extends Contracts[T][F]
|
||||
>(
|
||||
contractOrType: T,
|
||||
functionName: F,
|
||||
args: ParameterObjOfDescriptor<Descriptor>
|
||||
): Operation.PublicCall => {
|
||||
const contractCall = processContractCall(
|
||||
AlexContracts,
|
||||
contractOrType,
|
||||
functionName
|
||||
);
|
||||
const input = contractCall.encodeInput(args);
|
||||
// Broadcast public contract or send readonly call
|
||||
const output = contractCall.decodeOutput(response.output);
|
||||
return output;
|
||||
/**
|
||||
* Let's call a readonly function
|
||||
*/
|
||||
const contractDeployerAddress = "...";
|
||||
const contractName = "...";
|
||||
const readonlyFunctionName = "...";
|
||||
const readonlyFunctionArgs = {
|
||||
/* ... */
|
||||
};
|
||||
const functionDescriptor = AlexContracts[contractName][readonlyFunctionName];
|
||||
const clarityArgs = functionDescriptor.input.map((arg) =>
|
||||
arg.type.encode(readonlyFunctionArgs[arg.name])
|
||||
);
|
||||
const result = await callReadOnlyFunction({
|
||||
contractName,
|
||||
functionName: readonlyFunctionName,
|
||||
functionArgs: clarityArgs,
|
||||
contractAddress: contractDeployerAddress,
|
||||
senderAddress: contractDeployerAddress,
|
||||
});
|
||||
console.log("result", functionDescriptor.output.decode(result));
|
||||
|
||||
/**
|
||||
* Let's simply encode/decode some clarity value
|
||||
*/
|
||||
const schema = tupleT({
|
||||
hello: stringT,
|
||||
});
|
||||
const encoded = schema.encode({ hello: "world" });
|
||||
console.log("serialized clarityValue", encoded);
|
||||
console.log("deserialized clarityValue", schema.decode(encoded));
|
||||
```
|
||||
|
||||
### Make contract calls
|
||||
|
||||
```typescript
|
||||
import { makeContractCall, broadcastTransaction } from "@stacks/transactions";
|
||||
import { composeTxOptionsFactory, executeReadonlyCallFactory } from "clarity-codegen";
|
||||
import { AlexContracts } from "./generated/contracts_Alex";
|
||||
|
||||
const composeTxOptions = composeTxOptionsFactory(AlexContracts, {
|
||||
deployerAddress: "...",
|
||||
});
|
||||
const executeReadonlyCall = executeReadonlyCallFactory(AlexContracts, {
|
||||
deployerAddress: "...",
|
||||
});
|
||||
|
||||
// make a readonly call
|
||||
console.log("decoded readonly call result", await executeReadonlyCall({
|
||||
"contract name",
|
||||
"function name",
|
||||
{ /* arguments */ },
|
||||
}));
|
||||
|
||||
// create a public call
|
||||
const txOptions = composeTxOptions({
|
||||
"contract name",
|
||||
"function name",
|
||||
{ /* arguments */ },
|
||||
{
|
||||
postConditions: [ /* post conditions */ ],
|
||||
},
|
||||
});
|
||||
const tx = makeContractCall({ ...txOptions, senderKey: "..." });
|
||||
await broadcastTransaction(tx);
|
||||
```
|
||||
|
||||
### Processing Historical Transactions
|
||||
|
||||
65
src/runtime/composeTxOptions.ts
Normal file
65
src/runtime/composeTxOptions.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
AnchorMode,
|
||||
ContractCallOptions,
|
||||
FungiblePostCondition,
|
||||
PostConditionMode,
|
||||
STXPostCondition,
|
||||
} from "@stacks/transactions";
|
||||
import { StringOnly } from "../utils/helpers";
|
||||
import {
|
||||
ContractBaseType,
|
||||
OpenCallFunctionDescriptor,
|
||||
ParameterObjOfDescriptor,
|
||||
} from "./contractBase";
|
||||
|
||||
export type ComposeTxOptionsFn<Contracts extends ContractBaseType> = <
|
||||
T extends StringOnly<keyof Contracts>,
|
||||
F extends StringOnly<keyof Contracts[T]>,
|
||||
Descriptor extends Contracts[T][F]
|
||||
>(
|
||||
contractName: T,
|
||||
functionName: F,
|
||||
args: Descriptor extends OpenCallFunctionDescriptor
|
||||
? ParameterObjOfDescriptor<Descriptor>
|
||||
: never,
|
||||
options?: {
|
||||
postConditions?: (FungiblePostCondition | STXPostCondition)[];
|
||||
}
|
||||
) => ContractCallOptions;
|
||||
|
||||
export const composeTxOptionsFactory =
|
||||
<T extends ContractBaseType>(
|
||||
contracts: T,
|
||||
factoryOptions: {
|
||||
deployerAddress: string;
|
||||
}
|
||||
): ComposeTxOptionsFn<T> =>
|
||||
(contractName, functionName, args, options = {}) => {
|
||||
const functionDescriptor = contracts[contractName][functionName];
|
||||
|
||||
if (functionDescriptor.mode !== "public") {
|
||||
throw new Error(
|
||||
`[composeTx] function ${contractName}.${functionName} should be a public function`
|
||||
);
|
||||
}
|
||||
|
||||
const clarityArgs = functionDescriptor.input.map((arg) =>
|
||||
arg.type.encode(args[arg.name])
|
||||
);
|
||||
|
||||
const postConditionMode =
|
||||
options.postConditions == null
|
||||
? PostConditionMode.Allow
|
||||
: PostConditionMode.Deny;
|
||||
const postConditions = options.postConditions;
|
||||
|
||||
return {
|
||||
contractName,
|
||||
functionName,
|
||||
functionArgs: clarityArgs,
|
||||
contractAddress: factoryOptions.deployerAddress,
|
||||
anchorMode: AnchorMode.Any,
|
||||
postConditionMode,
|
||||
postConditions,
|
||||
};
|
||||
};
|
||||
73
src/runtime/executeReadonlyCall.ts
Normal file
73
src/runtime/executeReadonlyCall.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { callReadOnlyFunction } from "@stacks/transactions";
|
||||
import { StringOnly } from "../utils/helpers";
|
||||
import {
|
||||
ContractBaseType,
|
||||
ParameterObjOfDescriptor,
|
||||
ReadonlyFunctionDescriptor,
|
||||
ReturnTypeOfDescriptor,
|
||||
} from "./contractBase";
|
||||
|
||||
export type CallReadOnlyFunctionFn = typeof callReadOnlyFunction;
|
||||
|
||||
export type ExecuteReadonlyCallFn<Contracts extends ContractBaseType> = <
|
||||
T extends StringOnly<keyof Contracts>,
|
||||
F extends StringOnly<keyof Contracts[T]>,
|
||||
Descriptor extends Contracts[T][F]
|
||||
>(
|
||||
contractName: T,
|
||||
functionName: F,
|
||||
args: Descriptor extends ReadonlyFunctionDescriptor
|
||||
? ParameterObjOfDescriptor<Descriptor>
|
||||
: never,
|
||||
options?: {
|
||||
senderAddress?: string;
|
||||
callReadOnlyFunction?: CallReadOnlyFunctionFn;
|
||||
}
|
||||
) => Promise<
|
||||
Descriptor extends ReadonlyFunctionDescriptor
|
||||
? ReturnTypeOfDescriptor<Descriptor>
|
||||
: never
|
||||
>;
|
||||
|
||||
export const executeReadonlyCallFactory =
|
||||
<T extends ContractBaseType>(
|
||||
contracts: T,
|
||||
factoryOptions: {
|
||||
deployerAddress: string;
|
||||
defaultSenderAddress?: string;
|
||||
callReadOnlyFunction?: CallReadOnlyFunctionFn;
|
||||
}
|
||||
): ExecuteReadonlyCallFn<T> =>
|
||||
async (contractName, functionName, args, options = {}) => {
|
||||
const functionDescriptor = contracts[contractName][functionName];
|
||||
|
||||
if (functionDescriptor.mode !== "readonly") {
|
||||
throw new Error(
|
||||
`[composeTx] function ${contractName}.${functionName} should be a readonly function`
|
||||
);
|
||||
}
|
||||
|
||||
const clarityArgs = functionDescriptor.input.map((arg) =>
|
||||
arg.type.encode(args[arg.name])
|
||||
);
|
||||
|
||||
const senderAddress =
|
||||
options.senderAddress ??
|
||||
factoryOptions.defaultSenderAddress ??
|
||||
factoryOptions.deployerAddress;
|
||||
|
||||
const _callReadOnlyFunction =
|
||||
options.callReadOnlyFunction ??
|
||||
factoryOptions.callReadOnlyFunction ??
|
||||
callReadOnlyFunction;
|
||||
|
||||
const result = await _callReadOnlyFunction({
|
||||
contractName,
|
||||
functionName,
|
||||
functionArgs: clarityArgs,
|
||||
contractAddress: factoryOptions.deployerAddress,
|
||||
senderAddress,
|
||||
});
|
||||
|
||||
return functionDescriptor.output.decode(result);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Response } from "../runtime/types";
|
||||
|
||||
export type StringOnly<T> = Extract<T, string>
|
||||
|
||||
export function mapValues<T extends Record<string, any>, VO>(
|
||||
obj: T,
|
||||
mapping: <K extends keyof T>(value: T[K], key: K) => VO
|
||||
|
||||
Reference in New Issue
Block a user