Skip to content

Commit

Permalink
feat: add zkDrop template app (sismo-core#187)
Browse files Browse the repository at this point in the history
Co-authored-by: leo21.sismo.eth <[email protected]>
  • Loading branch information
gabin54 and leosayous21 authored Jul 11, 2023
1 parent 6d4b3d9 commit c15eee0
Show file tree
Hide file tree
Showing 37 changed files with 7,901 additions and 61 deletions.
73 changes: 73 additions & 0 deletions .github/workflows/upload-metdata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Upload zkDrop Metadata

on:
issue_comment:
types: [created]

jobs:
create-app:
runs-on: ubuntu-latest
if: contains(github.event.comment.body, '/upload-metadata')
steps:
- name: Check if sismo-core organisation member
id: is_organization_member
uses: jamessingleton/[email protected]
with:
organization: "sismo-core"
username: ${{ github.actor }}
token: ${{ secrets.GITHUB_TOKEN }}

- if: steps.is_organization_member.outputs.result != 'true'
run: exit 1

- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # fetch all branches and tags
token: ${{secrets.SISMOBOT_TOKEN}}

- name: Checkout Pull Request
run: hub pr checkout ${{ github.event.issue.number }}
env:
GITHUB_TOKEN: ${{ secrets.SISMOBOT_TOKEN }}

- name: Install dependencies
run: yarn install --frozen-lockfile

- id: changed-files
uses: ahmadnassri/action-changed-files@v1

- name: content
run: |
echo 'Changed files:'
echo "${{ steps.changed-files.outputs.files }}"
- name: Get modified spaces and apps
id: updated-spaces
run: |
echo 'Changed files:'
DEMO_UPDATED_SPACES=$(echo ${{steps.changed-files.outputs.files}} | awk 'BEGIN {OFS=FS=" "} {for(i=1; i<=NF; i++) {if ($i ~ /demo/){split($i,a,"/"); n=split(a[length(a)],b,"."); print b[1]}}}' | xargs)
echo "::set-output name=demo_updated_spaces::'$(echo "$DEMO_UPDATED_SPACES")'"
MAIN_UPDATED_SPACES=$(echo ${{steps.changed-files.outputs.files}} | awk 'BEGIN {OFS=FS=" "} {for(i=1; i<=NF; i++) {if ($i ~ /main/){split($i,a,"/"); n=split(a[length(a)],b,"."); print b[1]}}}' | xargs)
echo "::set-output name=main_updated_spaces::'$(echo "$MAIN_UPDATED_SPACES")'"
- name: Display changes
run: |
echo "[DEMO] Modified spaces and apps in commit:"
echo "${{ steps.updated-spaces.outputs.demo_updated_spaces }}"
echo "[MAIN] Modified spaces and apps in commit:"
echo "${{ steps.updated-spaces.outputs.main_updated_spaces }}"
- name: Run Upload metadata script [demo]
env:
NEXT_PUBLIC_NODE_ENV: demo
PINATA_JWT_TOKEN: ${{ secrets.PINATA_JWT_TOKEN }}
run: |
yarn upload-zk-drop-metadata ${{ steps.updated-spaces.outputs.demo_updated_spaces }}
- name: Run Upload metadata script [main]
env:
NEXT_PUBLIC_NODE_ENV: main
PINATA_JWT_TOKEN: ${{ secrets.PINATA_JWT_TOKEN }}
run: |
yarn upload-zk-drop-metadata ${{ steps.updated-spaces.outputs.main_updated_spaces }}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"prebuild": "yarn run build-config",
"build-config": "tsx ./src/scripts/configs-to-json/configs-to-json.script.ts",
"sync-all-apps-factory": "NEXT_PUBLIC_NODE_ENV=main yarn sync-apps-factory && NEXT_PUBLIC_NODE_ENV=demo yarn sync-apps-factory",
"upload-zk-drop-metadata": "yarn run build-config && tsx -r ./svgMock.js ./src/scripts/upload-metadata/upload-metadata.script.ts",
"sync-apps-factory": "yarn run build-config && tsx -r ./svgMock.js ./src/scripts/sync-apps-factory/sync-apps-factory.script.ts",
"prettier": "prettier --write 'src/**/*.ts' '.github' 'README.md'"
},
Expand All @@ -27,6 +28,7 @@
"axios": "^1.4.0",
"bufferutil": "^4.0.7",
"classnames": "^2.3.2",
"commander": "^11.0.0",
"defender-autotask-utils": "^1.44.0",
"defender-relay-client": "^1.44.0",
"eslint": "8.39.0",
Expand Down
2 changes: 1 addition & 1 deletion space-configs/demo/sismo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,6 @@ export default {
options: {
isFeatured: true,
},
},
}
],
} as SpaceConfig;
1 change: 1 addition & 0 deletions space-configs/demo/the-merge-contributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default {
type: "google_sheet",
spreadsheetId: "1Wrh8gFPWuUfdip1wuOxBx_0bZQ_OJb2JsI5A-loRa-Y",
},

},
},
options: {
Expand Down
1 change: 1 addition & 0 deletions space-configs/main/privacy-is-normal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export default {
},
},
},

},
],
} as SpaceConfig;
2 changes: 1 addition & 1 deletion space-configs/main/sismo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,6 @@ export default {
options: {
endDate: new Date("2023-07-07T18:00"),
},
},
}
],
} as SpaceConfig;
33 changes: 21 additions & 12 deletions space-configs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,6 @@ export type FirstComeFirstServed = {
maxNumberOfUsers: number;
};

export type ZkDropAppConfig = AppCommonConfig & {
type: "zkdrop";
templateConfig: {
step1CtaText?: string;
step2CtaText: string;
appDescription?: string;
chainId: number;
userSelection?: UserSelection; // default none
contractAddress: string;
};
};

export type ZkBadgeChainName = Network.Gnosis | Network.Mumbai | Network.Sepolia;
export type ZkBadgeAppConfig = AppCommonConfig & {
type: "zkBadge";
Expand All @@ -108,6 +96,27 @@ export type ZkBadgeAppConfig = AppCommonConfig & {
};
};

export type ZkDropChainName = Network.Gnosis | Network.Mumbai | Network.Sepolia;
export type ZkDropAppConfig = AppCommonConfig & {
type: "zkDrop";
templateConfig: {
nftMetadata: {
name: string;
description: string;
image: string;
};
chains: {
contractAddress: string;
name: ZkDropChainName;
isTransferable: boolean;
relayerEnabled?: boolean;
}[];
step1CtaText?: string;
step2CtaText: string;
appDescription?: string;
};
};

export type ZkFormAppConfig = AppCommonConfig & {
type: "zkForm";
templateConfig: {
Expand Down
4 changes: 0 additions & 4 deletions src/app/api/zk-badge/relay-tx/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ jest.mock("../../../../libs/contracts/signers", () => {
});

jest.mock('../../../../libs/contracts/zk-badge', () => {
// Use the 'mockImplementation' method to mock the 'ZkBadgeContract' class
const originalModule = jest.requireActual("../../../../libs/contracts/zk-badge");

return {
__esModule: true,
...originalModule,
Expand All @@ -36,9 +34,7 @@ describe('POST /api/zk-badge/relay-tx', () => {
tokenId: 'mockTokenId'
})
};

await POST(req);

expect(mockMint).toHaveBeenCalledWith({
responseBytes: 'mockResponseBytes',
address: 'mockDestination',
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/zk-badge/relay-tx/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import env from "@/src/environments";
import { Network } from "@/src/libs/contracts/networks";
import { getDefenderRelayerSigner } from "@/src/libs/contracts/signers";
import { ZkBadgeContract } from "@/src/libs/contracts/zk-badge";
Expand All @@ -6,7 +7,7 @@ import { NextResponse } from "next/server";
export async function POST(req: Request) {
const { responseBytes, destination, tokenId, chain } = await req.json();

const signer = getDefenderRelayerSigner(Network.Mumbai);
const signer = getDefenderRelayerSigner(Network.Mumbai, env.defenderAPIKeys.zkBadge);
const zkMinterContract = new ZkBadgeContract({ network: chain, signer });

try {
Expand Down
97 changes: 97 additions & 0 deletions src/app/api/zk-drop/relay-tx/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @jest-environment node
*/
import ServiceFactory from "@/src/services/service-factory/service-factory";
import { POST } from "./route";
import { spaceMock1, spaceMock2 } from "@/src/services/spaces-service/tests/spaces-mock";
import { Network } from "@/src/libs/contracts/networks";


const mockMint = jest.fn().mockImplementation(() => Promise.resolve({ hash: 'mockTxHash' }));

jest.mock("../../../../libs/contracts/signers", () => {
return {
getDefenderRelayerSigner: jest.fn().mockReturnValue({}),
};
});

jest.mock('../../../../libs/contracts/zk-drop', () => {
const originalModule = jest.requireActual("../../../../libs/contracts/zk-drop");
return {
__esModule: true,
...originalModule,
ZkDropContract: jest.fn().mockImplementation(() => {
return {
mint: mockMint,
};
}),
};
});

describe('POST /api/zk-drop/relay-tx', () => {
beforeEach(() => {
let spacesService = ServiceFactory.getSpacesService();
const configs = [
spaceMock1,
spaceMock2
]
spacesService.updateConfigs(configs);
})

afterEach(() => {
let configs = ServiceFactory.getSpaceConfigs();
let spacesService = ServiceFactory.getSpacesService();
spacesService.updateConfigs(configs);
jest.clearAllMocks();
})

it('Should throw with an incorrect app slug', async () => {
const req: any = {
json: jest.fn().mockResolvedValue({
responseBytes: 'mockResponseBytes',
destination: 'mockDestination',
spaceSlug: spaceMock1.metadata.slug,
appSlug: "zk-drop-slug-invalid",
chain: Network.Sepolia
})
};

const response = await POST(req);
const data = await response.json();
expect(data.code).toEqual(`No app found for ${spaceMock1.metadata.slug}/zk-drop-slug-invalid`);
});

it('Should throw with an incorrect chain', async () => {
const req: any = {
json: jest.fn().mockResolvedValue({
responseBytes: 'mockResponseBytes',
destination: 'mockDestination',
spaceSlug: spaceMock1.metadata.slug,
appSlug: "zk-drop-slug",
chain: Network.Mumbai
})
};

const response = await POST(req);
const data = await response.json();
expect(data.code).toEqual(`Chain mumbai not supported for the app ${spaceMock1.metadata.slug}/zk-drop-slug`);
});

it('Should call mint with the correct arguments', async () => {
const req: any = {
json: jest.fn().mockResolvedValue({
responseBytes: 'mockResponseBytes',
destination: 'mockDestination',
spaceSlug: spaceMock1.metadata.slug,
appSlug: "zk-drop-slug",
chain: Network.Sepolia
})
};
await POST(req);
console.log("Mock mint calls: ", mockMint.mock.calls);
expect(mockMint).toHaveBeenCalledWith({
responseBytes: 'mockResponseBytes',
address: 'mockDestination'
});
});
});
52 changes: 52 additions & 0 deletions src/app/api/zk-drop/relay-tx/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import env from "@/src/environments";
import { getDefenderRelayerSigner } from "@/src/libs/contracts/signers";
import { ZkDropContract } from "@/src/libs/contracts/zk-drop";
import ServiceFactory from "@/src/services/service-factory/service-factory";
import { ZkDropAppType } from "@/src/services/spaces-service";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
const { responseBytes, destination, chain, spaceSlug, appSlug } = await req.json();

const spacesService = ServiceFactory.getSpacesService();
const apps = await spacesService.getApps({ where: { spaceSlug, appSlug }});
if (!apps || apps.length !== 1) {
return NextResponse.json({
code: `No app found for ${spaceSlug}/${appSlug}`
})
}
const app = apps[0] as ZkDropAppType;

const signer = getDefenderRelayerSigner(chain, env.defenderAPIKeys.zkDrop);
const chainConfig = app.chains.find(_chain => _chain.name === chain)

if (!chainConfig) {
return NextResponse.json({
code: `Chain ${chain} not supported for the app ${spaceSlug}/${appSlug}`
})
}
const zkDropContract = new ZkDropContract({ signer, contractAddress: chainConfig.contractAddress });

try {
console.log({
responseBytes,
address: destination,
chain,
contractAddress: chainConfig.contractAddress
});
const tx = await zkDropContract.mint({
responseBytes,
address: destination
});

return NextResponse.json({
success: true,
txHash: tx.hash
});
} catch (e) {
console.error(e);
return NextResponse.json({
code: "minting-error"
})
}
}
8 changes: 4 additions & 4 deletions src/components/AppCardLarge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getHumanReadableRemainingTimeTag } from "@/src/utils/getHumanReadableTi
import SpaceTag from "../SpaceTag";
import Default from "@/src/assets/default.svg";
import colors from "@/src/themes/colors";
import { ExternalAppType, Lottery, ZkAppType, ZkDropAppType } from "@/src/services/spaces-service";
import { ExternalAppType, Lottery, ZkAppType, ZkDropAppType, ZkFormAppType } from "@/src/services/spaces-service";
import Link from "next/link";

const Container = styled(Link)<{ $isDisabled: boolean }>`
Expand Down Expand Up @@ -202,9 +202,9 @@ export default function AppCardLarge({ app }: Props): JSX.Element {
const isDisabled = hasEnded;

const maxNumberOfEntries =
(app?.type == "zkForm" || app?.type == "zkdrop") &&
(app as unknown as ZkDropAppType)?.userSelection?.type == "Lottery" &&
((app as unknown as ZkDropAppType)?.userSelection as Lottery)?.maxNumberOfEntries;
app?.type == "zkForm" &&
(app as unknown as ZkFormAppType)?.userSelection?.type == "Lottery" &&
((app as unknown as ZkFormAppType)?.userSelection as Lottery)?.maxNumberOfEntries;

const link = app?.type === "external" ? (app as unknown as ExternalAppType)?.link : `/${app.space.slug}/${app.slug}`;
if (app)
Expand Down
8 changes: 4 additions & 4 deletions src/components/AppCardSmall/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SpaceTag from "../SpaceTag";
import { useState } from "react";
import Default from "@/src/assets/default.svg";
import colors from "@/src/themes/colors";
import { ExternalAppType, ZkDropAppType, Lottery, ZkAppType } from "@/src/services/spaces-service";
import { ExternalAppType, ZkDropAppType, Lottery, ZkAppType, ZkFormAppType } from "@/src/services/spaces-service";
import Link from "next/link";

const CardContainer = styled.div<{ $isSeparator: boolean }>`
Expand Down Expand Up @@ -264,9 +264,9 @@ export default function AppCardLarge({ app, className, isSeparator }: Props): JS
const isDisabled = hasEnded;

const maxNumberOfEntries =
(app?.type == "zkForm" || app?.type == "zkdrop") &&
(app as unknown as ZkDropAppType)?.userSelection?.type == "Lottery" &&
((app as unknown as ZkDropAppType)?.userSelection as Lottery)?.maxNumberOfEntries;
app?.type == "zkForm" &&
(app as unknown as ZkFormAppType)?.userSelection?.type == "Lottery" &&
((app as unknown as ZkFormAppType)?.userSelection as Lottery)?.maxNumberOfEntries;

const link =
app?.type === "external"
Expand Down
Loading

0 comments on commit c15eee0

Please sign in to comment.