Skip to content

Commit

Permalink
feat: Notifications, contacts, bug fixes (#117)
Browse files Browse the repository at this point in the history
* add invite message

* add invite message

* small improvement to username

* simulation fails

* test chaining

* test chaining

* add double transact

* lower verification level

* chaining test

* permissions!

* clean up

* fix error

* add release update
  • Loading branch information
andy-t-wang authored Jan 6, 2025
1 parent 43be099 commit 5cc1849
Show file tree
Hide file tree
Showing 15 changed files with 378 additions and 82 deletions.
26 changes: 20 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- main

permissions:
contents: write
pull-requests: write
Expand Down Expand Up @@ -53,8 +53,15 @@ jobs:
- name: Build
run: pnpm build

- name: Publish to npm
run: pnpm publish --report-summary
- name: Publish Core to npm
working-directory: ./packages/core
run: pnpm publish --report-summary --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish React to npm
working-directory: ./packages/react
run: pnpm publish --report-summary --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Expand Down Expand Up @@ -83,7 +90,14 @@ jobs:
- name: Build
run: pnpm build

- name: Publish to GitHub
run: pnpm publish --report-summary
- name: Publish Core to GitHub
working-directory: ./packages/core
run: pnpm publish --report-summary --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish React to GitHub
working-directory: ./packages/react
run: pnpm publish --report-summary --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
145 changes: 145 additions & 0 deletions demo/with-next/components/ClientContent/RequestPermissions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
MiniKit,
RequestPermissionErrorCodes,
ResponseEvent,
Contact,
RequestPermissionPayload,
Permission,
} from "@worldcoin/minikit-js";
import { useCallback, useEffect, useState } from "react";
import { validateSchema } from "./helpers/validate-schema";
import * as yup from "yup";

const requestPermissionSuccessPayloadSchema = yup.object({
status: yup.string<"success">().equals(["success"]).required(),
version: yup.number().required(),
permission: yup.string<Permission>().oneOf(Object.values(Permission)),
timestamp: yup.string().required(),
});

const requestPermissionErrorPayloadSchema = yup.object({
error_code: yup
.string<RequestPermissionErrorCodes>()
.oneOf(Object.values(RequestPermissionErrorCodes))
.required(),
description: yup.string().required(),
status: yup.string<"error">().equals(["error"]).required(),
version: yup.number().required(),
});

export const RequestPermission = () => {
const [requestPermissionAppPayload, setRequestPermissionAppPayload] =
useState<string | undefined>();

const [
requestPermissionPayloadValidationMessage,
setRequestPermissionPayloadValidationMessage,
] = useState<string | null>();

const [sentRequestPermissionPayload, setSentRequestPermissionPayload] =
useState<Record<string, any> | null>(null);

const [tempInstallFix, setTempInstallFix] = useState(0);

useEffect(() => {
if (!MiniKit.isInstalled()) {
return;
}

MiniKit.subscribe(
ResponseEvent.MiniAppRequestPermission,
async (payload) => {
console.log("MiniAppRequestPermission, SUBSCRIBE PAYLOAD", payload);
setRequestPermissionAppPayload(JSON.stringify(payload, null, 2));
if (payload.status === "error") {
const errorMessage = await validateSchema(
requestPermissionErrorPayloadSchema,
payload
);

if (!errorMessage) {
setRequestPermissionPayloadValidationMessage("Payload is valid");
} else {
setRequestPermissionPayloadValidationMessage(errorMessage);
}
} else {
const errorMessage = await validateSchema(
requestPermissionSuccessPayloadSchema,
payload
);

// This checks if the response format is correct
if (!errorMessage) {
setRequestPermissionPayloadValidationMessage("Payload is valid");
} else {
setRequestPermissionPayloadValidationMessage(errorMessage);
}
}
}
);

return () => {
MiniKit.unsubscribe(ResponseEvent.MiniAppRequestPermission);
};
}, [tempInstallFix]);

const onRequestPermission = useCallback(async (permission: Permission) => {
const requestPermissionPayload: RequestPermissionPayload = {
permission,
};

const payload = MiniKit.commands.requestPermission(
requestPermissionPayload
);
setSentRequestPermissionPayload({
payload,
});
console.log("payload", payload);
setTempInstallFix((prev) => prev + 1);
}, []);

return (
<div>
<div className="grid gap-y-2">
<h2 className="text-2xl font-bold">Request Permission</h2>

<div>
<div className="bg-gray-300 min-h-[100px] p-2">
<pre className="break-all whitespace-break-spaces">
{JSON.stringify(sentRequestPermissionPayload, null, 2)}
</pre>
</div>
</div>
<div className="grid gap-4 grid-cols-2">
<button
className="bg-black text-white rounded-lg p-4 w-full"
onClick={() => onRequestPermission(Permission.Notifications)}
>
Request Notifications
</button>
</div>
</div>

<hr />

<div className="w-full grid gap-y-2">
<p>
Message from &quot;{ResponseEvent.MiniAppRequestPermission}&quot;{" "}
</p>

<div className="bg-gray-300 min-h-[100px] p-2">
<pre className="break-all whitespace-break-spaces">
{requestPermissionAppPayload ?? JSON.stringify(null)}
</pre>
</div>

<div className="grid gap-y-2">
<p>Response Validation:</p>
<p className="bg-gray-300 p-2">
{requestPermissionPayloadValidationMessage ?? "No validation"}
</p>
</div>
</div>
</div>
);
};
11 changes: 10 additions & 1 deletion demo/with-next/components/ClientContent/ShareContacts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ export const ShareContacts = () => {
}, [tempInstallFix]);

const onShareContacts = useCallback(
async (isMultiSelectEnabled: boolean = false) => {
async (isMultiSelectEnabled: boolean = false, inviteMessage?: string) => {
const shareContactsPayload: ShareContactsPayload = {
isMultiSelectEnabled,
inviteMessage,
};

const payload = MiniKit.commands.shareContacts(shareContactsPayload);
Expand Down Expand Up @@ -120,6 +121,14 @@ export const ShareContacts = () => {
Share Contacts (multi disabled)
</button>
</div>
<div className="grid gap-4 grid-cols-2">
<button
className="bg-black text-white rounded-lg p-4 w-full"
onClick={() => onShareContacts(false, "hello join worldcoin!")}
>
Share Contacts Invite Message
</button>
</div>
</div>

<hr />
Expand Down
53 changes: 50 additions & 3 deletions demo/with-next/components/ClientContent/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {
MiniKit,
ResponseEvent,
SendTransactionErrorCodes,
Tokens,
tokenToDecimals,
VerificationLevel,
} from "@worldcoin/minikit-js";
import { useWaitForTransactionReceipt } from "@worldcoin/minikit-react";
import { useEffect, useState } from "react";
Expand Down Expand Up @@ -141,7 +144,7 @@ export const SendTransaction = () => {
const permitTransfer = {
permitted: {
token: testTokens.worldchain.USDCE,
amount: "10000",
amount: "200000",
},
nonce: Date.now().toString(),
deadline,
Expand All @@ -155,7 +158,7 @@ export const SendTransaction = () => {

const transferDetails = {
to: "0x126f7998Eb44Dd2d097A8AB2eBcb28dEA1646AC8",
requestedAmount: "10000",
requestedAmount: "200000",
};

const transferDetailsArgsForm = [
Expand Down Expand Up @@ -394,6 +397,36 @@ export const SendTransaction = () => {
setTransactionData(payload);
};

const doubleAction = async () => {
const payload = await MiniKit.commandsAsync.verify({
action: process.env.NEXT_PUBLIC_STAGING_VERIFY_ACTION || "",
signal: "123",
verification_level: VerificationLevel.Device,
});
const pay = await MiniKit.commandsAsync.pay({
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
tokens: [
{
symbol: Tokens.WLD,
token_amount: tokenToDecimals(0.1, Tokens.WLD).toString(),
},
],
description: "Test Chaining",
reference: new Date().toISOString(),
});
// onSendTransactionClick();
};

const doubleActionTransact = async () => {
const payload = await MiniKit.commandsAsync.verify({
action: process.env.NEXT_PUBLIC_STAGING_VERIFY_ACTION || "",
signal: "123",
verification_level: VerificationLevel.Device,
});

onSendTransactionClick();
};

return (
<div className="grid gap-y-2">
<h2 className="text-2xl font-bold">Transaction</h2>
Expand All @@ -413,7 +446,7 @@ export const SendTransaction = () => {
className="bg-black text-white rounded-lg p-4 w-full"
onClick={onSendTransactionClick}
>
Send Transaction
Simulation Fails
</button>

<button
Expand All @@ -437,6 +470,20 @@ export const SendTransaction = () => {
Send Orb
</button>
</div>
<div className="grid gap-x-2 grid-cols-2">
<button
className="bg-black text-white rounded-lg p-4 w-full"
onClick={doubleAction}
>
Test Chaining Pay
</button>
<button
className="bg-black text-white rounded-lg p-4 w-full"
onClick={doubleActionTransact}
>
Test Chaining Transact
</button>
</div>

<div className="grid gap-y-1">
<p>
Expand Down
3 changes: 3 additions & 0 deletions demo/with-next/components/ClientContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SendTransaction } from "./Transaction";
import { SignMessage } from "./SignMessage";
import { SignTypedData } from "./SignTypedMessage";
import { ShareContacts } from "./ShareContacts";
import { RequestPermission } from "./RequestPermissions";
import {
GetSearchedUsernameResult,
UsernameSearch,
Expand Down Expand Up @@ -98,6 +99,8 @@ export const ClientContent = () => {
<hr />
<ShareContacts />
<hr />
<RequestPermission />
<hr />
<input className="text-xs border-black border-2" />
<ExternalLinks />
<hr />
Expand Down
4 changes: 2 additions & 2 deletions packages/core/helpers/payment/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PayCommandInput, Tokens } from "types";
import { TokenDecimals } from "types/payment";
import { PayCommandInput } from "types/commands";
import { TokenDecimals, Tokens } from "types/payment";

// This is a helper function to convert token amount to decimals for payment
// Amount should be in expected amount ie $25.12 should be 25.12
Expand Down
5 changes: 3 additions & 2 deletions packages/core/helpers/siwe/siwe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getContract,
Client,
} from "viem";
import { optimism, worldchain } from "viem/chains";
import { worldchain } from "viem/chains";

const PREAMBLE = " wants you to sign in with your Ethereum account:";
const URI_TAG = "URI: ";
Expand Down Expand Up @@ -189,7 +189,8 @@ export const verifySiweMessage = async (

// Check ERC-191 Signature Matches
let provider =
userProvider || createPublicClient({ chain: worldchain, transport: http() });
userProvider ||
createPublicClient({ chain: worldchain, transport: http() });
const signedMessage = `${ERC_191_PREFIX}${message.length}${message}`;
const messageBytes = Buffer.from(signedMessage, "utf8").toString("hex");
const hashedMessage = hashMessage(signedMessage);
Expand Down
14 changes: 7 additions & 7 deletions packages/core/helpers/usernames/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export const getUserProfile = async (address: string) => {
const res = await fetch('https://usernames.worldcoin.org/api/v1/query', {
method: 'POST',
const res = await fetch("https://usernames.worldcoin.org/api/v1/query", {
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
body: JSON.stringify({
addresses: [address]
})
})
addresses: [address],
}),
});

const usernames = await res.json();
return usernames[0] ?? { username: null, profilePictureUrl: null };
return usernames?.[0] ?? { username: null, profilePictureUrl: null };
};
10 changes: 2 additions & 8 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
export { MiniKit } from "./minikit";

export {
VerifyCommandInput,
PayCommandInput,
Command,
SiweMessage,
} from "./types";

export * from "./types/responses";
export * from "./types/commands";
export * from "./types/errors";
export * from "./types/payment";
export * from "./types/wallet-auth";

export { Tokens, Network, TokenDecimals } from "./types/payment";
export { tokenToDecimals } from "helpers/payment/client";

export { VerificationLevel, type ISuccessResult } from "@worldcoin/idkit-core";
Expand Down
Loading

0 comments on commit 5cc1849

Please sign in to comment.