feat: add composeTxOptions, executeReadonlyCall

This commit is contained in:
c4605
2024-04-04 13:25:56 +01:00
parent 1a04ece465
commit 5ba725e577
4 changed files with 206 additions and 23 deletions

View File

@@ -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

View 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,
};
};

View 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);
};

View File

@@ -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