Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/server/routes/contract/write/write.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type Static, Type } from "@sinclair/typebox";
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { prepareContractCall, resolveMethod } from "thirdweb";
import { type AbiFunction, parseAbiParams } from "thirdweb/utils";
import { parseAbiParams, type AbiFunction } from "thirdweb/utils";
import { getContractV5 } from "../../../../utils/cache/getContractv5";
import { prettifyError } from "../../../../utils/error";
import { queueTransaction } from "../../../../utils/transaction/queueTransation";
Expand All @@ -19,17 +19,26 @@ import {
requiredAddress,
walletWithAAHeaderSchema,
} from "../../../schemas/wallet";
import { sanitizeAbi } from "../../../utils/abi";
import { sanitizeAbi, sanitizeFunctionName } from "../../../utils/abi";
import { getChainIdFromChain } from "../../../utils/chain";
import { parseTransactionOverrides } from "../../../utils/transactionOverrides";

// INPUT
const writeRequestBodySchema = Type.Object({
functionName: Type.String({
description: "The function to call on the contract",
description: `The function to call on the contract. It is highly recommended to provide a full function signature, such as "function mintTo(address to, uint256 amount)", to avoid ambiguity and to skip ABI resolution.`,
examples: ["function mintTo(address to, uint256 amount)"],
}),
args: Type.Array(Type.Any(), {
description: "The arguments to call on the function",
description:
"An array of arguments to provide the function. Supports: numbers, strings, arrays, objects. Do not provide: BigNumber, bigint, Date objects",
examples: [
[
1730380951,
"0x09530565aC1Ce08C3621f5B24Fca6d9a76574620",
["a", "b", "c"],
],
],
}),
...txOverridesWithValueSchema.properties,
abi: Type.Optional(abiArraySchema),
Expand Down Expand Up @@ -85,7 +94,8 @@ export async function writeToContract(fastify: FastifyInstance) {
let method: AbiFunction;
let params: Array<string | bigint | boolean | object>;
try {
method = await resolveMethod(functionName)(contract);
const functionNameOrSignature = sanitizeFunctionName(functionName);
method = await resolveMethod(functionNameOrSignature)(contract);
params = parseAbiParams(
method.inputs.map((i) => i.type),
args,
Expand All @@ -97,6 +107,7 @@ export async function writeToContract(fastify: FastifyInstance) {
"BAD_REQUEST",
);
}

const transaction = prepareContractCall({
contract,
method,
Expand Down
8 changes: 7 additions & 1 deletion src/server/utils/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { Abi } from "thirdweb/utils";
import type { AbiSchemaType } from "../schemas/contract";

export function sanitizeAbi(abi: AbiSchemaType | undefined): Abi | undefined {
if (!abi) return undefined;
if (!abi) {
return undefined;
}

return abi.map((item) => {
if (item.type === "function") {
return {
Expand All @@ -15,3 +18,6 @@ export function sanitizeAbi(abi: AbiSchemaType | undefined): Abi | undefined {
return item;
}) as Abi;
}

export const sanitizeFunctionName = (val: string) =>
val.includes("(") && !val.startsWith("function ") ? `function ${val}` : val;
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { beforeAll, describe, expect, test } from "bun:test";
import assert from "node:assert";
import { type Address, stringToHex } from "thirdweb";
import { zeroAddress } from "viem";
import type { ApiError } from "../../../sdk/dist/thirdweb-dev-engine.cjs";
import { CONFIG } from "../config";
import type { setupEngine } from "../utils/engine";
import { pollTransactionStatus } from "../utils/transactions";
import { setup } from "./setup";
import type { ApiError } from "../../../../sdk/dist/thirdweb-dev-engine.cjs.js";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this test file into the routes folder

import { CONFIG } from "../../config";
import type { setupEngine } from "../../utils/engine";
import { pollTransactionStatus } from "../../utils/transactions";
import { setup } from "../setup";

describe("Write Tests", () => {
describe("/contract/write route", () => {
let tokenContractAddress: string;
let engine: ReturnType<typeof setupEngine>;
let backendWallet: Address;
Expand Down Expand Up @@ -183,6 +183,28 @@ describe("Write Tests", () => {
expect(writeTransactionStatus.minedAt).toBeDefined();
});

test("Write to a contract with function signature without prefix", async () => {
const writeRes = await engine.contract.write(
CONFIG.CHAIN.id.toString(),
tokenContractAddress,
backendWallet,
{
functionName: "setContractURI(string uri)",
args: ["https://signature-test.com"],
},
);

expect(writeRes.result.queueId).toBeDefined();

const writeTransactionStatus = await pollTransactionStatus(
engine,
writeRes.result.queueId,
true,
);

expect(writeTransactionStatus.minedAt).toBeDefined();
});

test("Write to a contract with function abi", async () => {
const writeRes = await engine.contract.write(
CONFIG.CHAIN.id.toString(),
Expand Down