diff --git a/.changeset/blue-eels-visit.md b/.changeset/blue-eels-visit.md
new file mode 100644
index 00000000..2ccb7e2f
--- /dev/null
+++ b/.changeset/blue-eels-visit.md
@@ -0,0 +1,5 @@
+---
+'@soundxyz/sdk': patch
+---
+
+Add calldata compression to mint function
diff --git a/examples/nextjs/src/app/v2/page.tsx b/examples/nextjs/src/app/v2/page.tsx
index 0093d86a..ed63039c 100644
--- a/examples/nextjs/src/app/v2/page.tsx
+++ b/examples/nextjs/src/app/v2/page.tsx
@@ -91,8 +91,8 @@ function EditionSchedule({ schedule }: { schedule: SuperMinterSchedule }) {
(schedule.tier === 0
? apiInfo?.data?.gaCoverImage?.url
: schedule.tier === 1
- ? apiInfo?.data?.vipCoverImage?.url
- : null) ?? apiInfo?.data?.coverImage.url
+ ? apiInfo?.data?.vipCoverImage?.url
+ : null) ?? apiInfo?.data?.coverImage.url
return (
@@ -100,8 +100,8 @@ function EditionSchedule({ schedule }: { schedule: SuperMinterSchedule }) {
{schedule.mode === 'VERIFY_MERKLE'
? 'Presale Limited Edition'
: schedule.tier === 0
- ? 'Forever Edition'
- : 'Limited Edition'}
+ ? 'Forever Edition'
+ : 'Limited Edition'}
{coverImage ?
: null}
diff --git a/package.json b/package.json
index 95f9e2bd..1555b24b 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,7 @@
"eslint-plugin-prettier": "^5.0.1",
"husky": "^8.0.3",
"lint-staged": "^15.0.1",
- "prettier": "^3.0.3",
+ "prettier": "^3.1.1",
"rimraf": "^5.0.5",
"semver": "^7.5.4",
"typescript": "5.2.2",
diff --git a/packages/legacy-sdk/package.json b/packages/legacy-sdk/package.json
index 1ff7c91f..44aee8db 100644
--- a/packages/legacy-sdk/package.json
+++ b/packages/legacy-sdk/package.json
@@ -30,9 +30,7 @@
"prepack": "node build.mjs",
"prepare": "node build.mjs",
"pull-env": "dotenv-vault pull",
- "test": "vitest dev",
- "test:cov": "vitest dev --coverage",
- "test:ci": "CI=true vitest --coverage",
+ "test": "vitest",
"tsc": "tsc -p tsconfig.build.json"
},
"dependencies": {
diff --git a/packages/legacy-sdk/src/client/edition/mint.ts b/packages/legacy-sdk/src/client/edition/mint.ts
index f3734cba..84bab4c2 100644
--- a/packages/legacy-sdk/src/client/edition/mint.ts
+++ b/packages/legacy-sdk/src/client/edition/mint.ts
@@ -162,14 +162,14 @@ async function mintHelper(
abi: minterAbiMap[interfaceId],
})
: interfaceId === interfaceIds.IMerkleDropMinterV2
- ? client.readContract({
- ...params,
- abi: minterAbiMap[interfaceId],
- })
- : client.readContract({
- ...params,
- abi: minterAbiMap[interfaceId],
- }))
+ ? client.readContract({
+ ...params,
+ abi: minterAbiMap[interfaceId],
+ })
+ : client.readContract({
+ ...params,
+ abi: minterAbiMap[interfaceId],
+ }))
proof = await getMerkleProof.call(this, {
merkleRoot,
diff --git a/packages/legacy-sdk/src/types.ts b/packages/legacy-sdk/src/types.ts
index 4e4113d0..d79404ba 100644
--- a/packages/legacy-sdk/src/types.ts
+++ b/packages/legacy-sdk/src/types.ts
@@ -113,8 +113,8 @@ export declare type PromiseOrValue
= T | Promise
type TupleSplit = O['length'] extends N
? [O, T]
: T extends readonly [infer F, ...infer R]
- ? TupleSplit
- : [O, T]
+ ? TupleSplit
+ : [O, T]
export type TakeFirst = TupleSplit[0]
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index c7109dd3..4566f580 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -35,6 +35,8 @@
"prepack": "node build.mjs",
"prepare": "node build.mjs",
"postpublish": "gh-release",
+ "test": "vitest",
+ "tsc": "tsc -p tsconfig.build.json",
"validate": "graphql-inspector validate \"./graphql/*.gql\" \"./schema.graphql\" --deprecated"
},
"devDependencies": {
@@ -49,7 +51,7 @@
"bob-watch": "^0.1.2",
"concurrently": "^8.2.1",
"esbuild": "^0.19.4",
- "prettier": "^3.0.3",
+ "prettier": "^3.1.1",
"typescript": "5.2.2",
"viem": "^1.20.0",
"zod": "^3.22.4"
diff --git a/packages/sdk/src/contract/edition-v2/write/mint.ts b/packages/sdk/src/contract/edition-v2/write/mint.ts
index 9c3710cc..3ea664b1 100644
--- a/packages/sdk/src/contract/edition-v2/write/mint.ts
+++ b/packages/sdk/src/contract/edition-v2/write/mint.ts
@@ -1,17 +1,30 @@
-import type { Chain, WalletClient } from 'viem'
+import { encodeFunctionData, type WalletClient } from 'viem'
import { curry } from '../../../utils/helpers'
import type { EditionMintContractInput } from '../read/mint'
+import { cdCompress } from '../../../utils/calldata'
-export function editionMint>(
+export function editionMint>(
client: Client,
{ input }: EditionMintContractInput,
) {
- return client.writeContract(input)
+ const calldata = encodeFunctionData({ abi: input.abi, functionName: input.functionName, args: input.args })
+ const compressedCalldata = cdCompress(calldata)
+
+ return client.sendTransaction({
+ account: input.account,
+ chain: input.chain,
+ value: input.value,
+ to: input.address,
+ data: compressedCalldata,
+ gas: input.gas,
+ maxFeePerGas: input.maxFeePerGas,
+ maxPriorityFeePerGas: input.maxPriorityFeePerGas,
+ })
}
-export function editionV2WalletActionsMint & { editionV2?: {} }>(
- client: Client,
-) {
+export function editionV2WalletActionsMint<
+ Client extends Pick & { editionV2?: {} },
+>(client: Client) {
return {
editionV2: {
...client.editionV2,
diff --git a/packages/sdk/src/contract/version.ts b/packages/sdk/src/contract/version.ts
index 9bdbcfe6..0a41c186 100644
--- a/packages/sdk/src/contract/version.ts
+++ b/packages/sdk/src/contract/version.ts
@@ -19,6 +19,7 @@ export function soundEditionVersionPublicActions {
+ test('LibZip: Calldata compress', () => {
+ const data =
+ '0xac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005b70e00000000000000000000000000000000000000000000000000000dfc79825feb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000645c48a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005b70e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a100000000000000000000000000000000000000000000000000000000'
+ const expected =
+ '0x5369af27001e20001e04001e80001d0160001d0220001d02a0001ea40c49ccbe001c05b70e00190dfc79825feb005b645c48a7003a84fc6f7865001c05b70e002f008f000f008f003a4449404b7c002b1f1cdf1a632eaaab40d1c263edf49faf749010a1003a64df2ab5bb000b7f5c764cbc14f9669b88837ca1490cca17c31607002b1f1cdf1a632eaaab40d1c263edf49faf749010a1001b'
+ expect(cdCompress(data)).toEqual(expected)
+ })
+})
+
+describe('cdDecompress', () => {
+ test('LibZip: Calldata decompress on invalid input', () => {
+ const data = '0xffffffff00ff'
+ const expected =
+ '0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+ expect(cdDecompress(data)).toEqual(expected)
+ })
+})
diff --git a/packages/sdk/src/utils/calldata.ts b/packages/sdk/src/utils/calldata.ts
new file mode 100644
index 00000000..fd60c808
--- /dev/null
+++ b/packages/sdk/src/utils/calldata.ts
@@ -0,0 +1,106 @@
+/* eslint-disable no-bitwise */
+
+import type { Hex } from 'viem'
+
+/**
+ * typescript adaptations of cdCompress and cdDecompress from
+ * https://github.com/Vectorized/solady/blob/main/js/solady.js
+ *
+ * these are custom calldata compression and decompression functions
+ * that are supported by Sound contracts (sound creator v2, sound edition v2, and super minter)
+ * and can reduce calldata size (biggest influence on L2 gas costs) when there are lots of zeros or lots of 0xffs
+ */
+export function cdCompress(original: Hex): Hex {
+ const data = hexString(original)
+ let output: Hex = '0x'
+ let zeroCount: number = 0
+ let ffCount: number = 0
+
+ const pushByte = (b: number): void => {
+ output += byteToString((+(output.length < 4 * 2 + 2) * 0xff) ^ b)
+ }
+
+ const rle = (value: number, distance: number): void => {
+ pushByte(0x00)
+ pushByte(distance - 1 + value * 0x80)
+ }
+
+ for (let i = 0; i < data.length; i += 2) {
+ const currentByte: number = parseByte(data, i)
+ if (currentByte === 0) {
+ if (ffCount) {
+ rle(1, ffCount)
+ ffCount = 0
+ }
+ if (++zeroCount === 0x80) {
+ rle(0, 0x80)
+ zeroCount = 0
+ }
+ continue
+ }
+ if (currentByte === 0xff) {
+ if (zeroCount) {
+ rle(0, zeroCount)
+ zeroCount = 0
+ }
+ if (++ffCount === 0x20) {
+ rle(1, 0x20)
+ ffCount = 0
+ }
+ continue
+ }
+ if (ffCount) {
+ rle(1, ffCount)
+ ffCount = 0
+ }
+ if (zeroCount) {
+ rle(0, zeroCount)
+ zeroCount = 0
+ }
+ pushByte(currentByte)
+ }
+ if (ffCount) {
+ rle(1, ffCount)
+ }
+ if (zeroCount) {
+ rle(0, zeroCount)
+ }
+ return output
+}
+
+export function cdDecompress(compressed: Hex): Hex {
+ const data = hexString(compressed)
+ let output: Hex = '0x'
+
+ for (let i = 0; i < data.length; ) {
+ let c: number = (+(i < 4 * 2) * 0xff) ^ parseByte(data, i)
+ i += 2
+ if (!c) {
+ c = (+(i < 4 * 2) * 0xff) ^ parseByte(data, i)
+ const size: number = (c & 0x7f) + 1
+ i += 2
+ for (let j = 0; j < size; ++j) {
+ output += byteToString((c >> 7 !== 0 && j < 32 ? 1 : 0) * 0xff)
+ }
+ continue
+ }
+ output += byteToString(c)
+ }
+ return output
+}
+
+function hexString(data: Hex): string {
+ const match = data.trim().match(/^(0x)?([0-9A-Fa-f]*)$/)
+ if (match && match[2] && match[2].length % 2 === 0) {
+ return match[2]
+ }
+ throw new Error('Data must be a valid hex string with even length.')
+}
+
+function byteToString(b: number): string {
+ return (b | 0x100).toString(16).substr(1)
+}
+
+function parseByte(data: string, i: number): number {
+ return parseInt(data.substr(i, 2), 16)
+}
diff --git a/packages/sdk/vitest.config.ts b/packages/sdk/vitest.config.ts
new file mode 100644
index 00000000..a842b332
--- /dev/null
+++ b/packages/sdk/vitest.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ benchmark: {
+ outputFile: './bench/report.json',
+ reporters: process.env['CI'] ? ['json'] : ['verbose'],
+ },
+ coverage: {
+ reporter: process.env['CI'] ? ['lcov'] : ['text', 'json', 'html'],
+ exclude: ['**/errors/utils.ts', '**/dist/**', '**/*.test.ts', '**/_test/**'],
+ },
+ environment: 'node',
+ // setupFiles: ['./test/setup.ts'],
+ // globalSetup: ['./test/globalSetup.ts'],
+ testTimeout: 30_000,
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8630fa04..113e5c43 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -69,7 +69,7 @@ importers:
specifier: ^15.0.1
version: 15.2.0
prettier:
- specifier: ^3.0.3
+ specifier: ^3.1.1
version: 3.1.1
rimraf:
specifier: ^5.0.5
@@ -255,7 +255,7 @@ importers:
specifier: ^0.19.4
version: 0.19.9
prettier:
- specifier: ^3.0.3
+ specifier: ^3.1.1
version: 3.1.1
typescript:
specifier: 5.2.2
diff --git a/tsconfig.json b/tsconfig.json
index dfa78d1d..20fada97 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -103,5 +103,5 @@
"include": ["packages"],
// subgraph typechecking is not necessary since the build scripts there convert AS code
- "exclude": ["node_modules", "packages/subgraph"]
+ "exclude": ["node_modules", "**/*.test.ts", "vitest.config.ts"]
}