diff --git a/package.json b/package.json index ddd756b..3b5e57b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,9 @@ "typescript": "^4.6.3", "yargs": "^17.4.0" }, + "dependencies": { + "@stacks/stacks-blockchain-api-types": "^3.0.2" + }, "peerDependencies": { "@stacks/transactions": "*" } diff --git a/src/index.ts b/src/index.ts index ef7a5ec..7052866 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from "./runtime/contractBase"; export * from "./runtime/transcoders"; +export * from "./runtime/decodeContractCallTransaction"; diff --git a/src/runtime/contractBase.ts b/src/runtime/contractBase.ts index 2b1c31c..6a241ef 100644 --- a/src/runtime/contractBase.ts +++ b/src/runtime/contractBase.ts @@ -61,12 +61,14 @@ export type ReturnTypeOfDescriptor = D extends FunctionDescriptor : never : never; +export type ContractBaseType = { + [contracts: string]: { + [func: string]: FunctionDescriptor; + }; +}; + export function defineContract< - T extends { - [contracts: string]: { - [func: string]: FunctionDescriptor; - }; - } + T extends ContractBaseType >(contracts: T): T { return contracts; } diff --git a/src/runtime/decodeContractCallTransaction.ts b/src/runtime/decodeContractCallTransaction.ts new file mode 100644 index 0000000..33e7a22 --- /dev/null +++ b/src/runtime/decodeContractCallTransaction.ts @@ -0,0 +1,91 @@ +import { ContractCallTransaction } from "@stacks/stacks-blockchain-api-types"; +import { deserializeCV } from "@stacks/transactions"; +import { + ContractBaseType, + FunctionDescriptor, + ParameterObjOfDescriptor, + ReturnTypeOfDescriptor, +} from "./contractBase"; + +export type ContractCallTransactionResultMap = { + [C in keyof Contracts]: { + [F in keyof Contracts[C]]: { + contractName: C; + functionName: F; + args: ParameterObjOfDescriptor; + result: ReturnTypeOfDescriptor; + }; + }; +}; + +export type ContractCallTransactionResult = { + [K in keyof ContractCallTransactionResultMap]: ContractCallTransactionResultMap[K][keyof ContractCallTransactionResultMap[K]]; +}[keyof ContractCallTransactionResultMap]; + +export function decodeContractCallTransaction< + Contracts extends ContractBaseType +>( + contracts: Contracts, + tx: ContractCallTransaction +): ContractCallTransactionResult { + if (!(tx.contract_call.contract_id in contracts)) { + throw new Error( + `[decodeContractCallTransaction] unknown contract id ${tx.contract_call.contract_id}` + ); + } + const contractName = tx.contract_call.contract_id as keyof Contracts; + + if (!(tx.contract_call.function_name in contracts[contractName])) { + throw new Error( + `[decodeContractCallTransaction] unknown function name ${contractName}.${tx.contract_call.function_name}` + ); + } + const functionName = tx.contract_call + .function_name as keyof Contracts[keyof Contracts]; + + return { + contractName, + functionName, + ...decodeSpecifiedContractCallTransaction( + contracts, + contractName, + functionName, + tx + ), + }; +} + +export function decodeSpecifiedContractCallTransaction< + Contracts extends ContractBaseType, + T extends keyof Contracts, + F extends keyof Contracts[T] +>( + contracts: Contracts, + contractOrType: T, + functionName: F, + tx: ContractCallTransaction +): { + args: ParameterObjOfDescriptor; + result: ReturnTypeOfDescriptor; + raw: ContractCallTransaction; +} { + const functionDescriptor = contracts[contractOrType][ + functionName + ] as any as FunctionDescriptor; + + const args = functionDescriptor.input.reduce( + (acc, arg, index) => ({ + ...acc, + [arg.name]: arg.type.decode( + deserializeCV(tx.contract_call.function_args![index]!.hex) + ), + }), + {} as Record + ); + + const result = functionDescriptor.output.decode( + deserializeCV(tx.tx_result.hex) + ); + + return { args: args as any, result, raw: tx }; +} diff --git a/yarn.lock b/yarn.lock index 91b663a..9f3a193 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,6 +29,11 @@ dependencies: "@stacks/common" "^3.3.0" +"@stacks/stacks-blockchain-api-types@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@stacks/stacks-blockchain-api-types/-/stacks-blockchain-api-types-3.0.2.tgz#f291f4f0cd28b26b9007debd16c688ca6e20a724" + integrity sha512-HurLltAqFVP2raQim9iQ6Gf0QH76hvfA22vHBeCf8lcU//4tw4MvFtPxl/MkspH4BqaoM+yqJ7rHpRV9TDgCCg== + "@stacks/transactions@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@stacks/transactions/-/transactions-3.3.0.tgz#ccc489196dc406d06271631f28f41937b96de897"