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
5 changes: 5 additions & 0 deletions .changeset/friendly-toys-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/react-native": patch
---

Migrate embedded wallet to new API in React Native
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export async function validateEmailOTP({

try {
const storedToken: AuthStoredTokenWithCookieReturnType["storedToken"] = {
jwtToken: verifiedToken.rawToken,
jwtToken: verifiedToken.jwtToken,
authDetails: verifiedToken.authDetails,
authProvider: verifiedToken.authProvider,
developerClientId: verifiedToken.developerClientId,
Expand All @@ -135,15 +135,16 @@ export async function validateEmailOTP({

export async function socialLogin(oauthOptions: OauthOption, clientId: string) {
const headlessLoginLinkWithParams = `${ROUTE_HEADLESS_GOOGLE_LOGIN}?authProvider=${encodeURIComponent(
"google",
oauthOptions.provider,
)}&baseUrl=${encodeURIComponent(
"https://ews.thirdweb.com",
"https://embedded-wallet.thirdweb.com",
)}&platform=${encodeURIComponent("mobile")}`;

const resp = await fetch(headlessLoginLinkWithParams);

if (!resp.ok) {
throw new Error("Error getting headless login link");
const error = await resp.json();
throw new Error(`Error getting headless login link: ${error.message}`);
}

const json = await resp.json();
Expand Down Expand Up @@ -211,29 +212,28 @@ export async function customJwt(authOptions: AuthOptions, clientId: string) {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jwtToken: jwt,
authProvider: AuthProvider.CUSTOM_JWT,
jwt: jwt,
developerClientId: clientId,
}),
});
if (!resp.ok) {
const { error } = await resp.json();
throw new Error(`JWT authentication error: ${error} `);
const error = await resp.json();
throw new Error(`JWT authentication error: ${error.message} `);
}

try {
const { verifiedToken, verifiedTokenJwtString } = await resp.json();

const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = {
jwtToken: verifiedToken.rawToken,
jwtToken: verifiedToken.jwtToken,
authProvider: verifiedToken.authProvider,
authDetails: {
...verifiedToken.authDetails,
email: verifiedToken.authDetails.email,
},
developerClientId: verifiedToken.developerClientId,
cookieString: verifiedTokenJwtString,
shouldStoreCookieString: false,
shouldStoreCookieString: true,
isNewUser: verifiedToken.isNewUser,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
import { CognitoUserSession } from "amazon-cognito-identity-js";
import {
ROUTE_GET_EMBEDDED_WALLET_DETAILS,
ROUTE_INIT_RECOVERY_CODE_FREE_WALLET,
ROUTE_STORE_USER_SHARES,
ROUTE_VERIFY_THIRDWEB_CLIENT_ID,
ROUTE_VERIFY_COGNITO_OTP,
Expand All @@ -14,7 +13,7 @@ import { getAuthTokenClient } from "../storage/local";
import * as Application from "expo-application";

const EMBEDDED_WALLET_TOKEN_HEADER = "embedded-wallet-token";
const PAPER_CLIENT_ID_HEADER = "x-paper-client-id";
const PAPER_CLIENT_ID_HEADER = "x-thirdweb-client-id";
const BUNDLE_ID_HEADER = "x-bundle-id";
const APP_BUNDLE_ID = Application.applicationId || "";

Expand All @@ -28,9 +27,9 @@ export const verifyClientId = async (clientId: string) => {
body: JSON.stringify({ clientId, parentDomain: "" }),
});
if (!resp.ok) {
const { error } = await resp.json();
const error = await resp.json();
throw new Error(
`Something went wrong generating auth token from user cognito email otp. ${error}`,
`Something went wrong generating auth token from user cognito email otp. ${error.message}`,
);
}
return {
Expand Down Expand Up @@ -66,17 +65,13 @@ export const authFetchEmbeddedWalletUser = async (

export async function getEmbeddedWalletUserDetail(args: {
email?: string;
userWalletId?: string;
clientId: string;
}) {
const url = new URL(ROUTE_GET_EMBEDDED_WALLET_DETAILS);
if (args) {
if (args.email) {
url.searchParams.append("email", args.email);
}
if (args.userWalletId) {
url.searchParams.append("userWalletId", args.userWalletId);
}
url.searchParams.append("clientId", args.clientId);
}
const resp = await authFetchEmbeddedWalletUser(
Expand All @@ -87,12 +82,15 @@ export async function getEmbeddedWalletUserDetail(args: {
},
);
if (!resp.ok) {
const { error } = await resp.json();
throw new Error(`Something went wrong determining wallet type. ${error}`);
const error = await resp.json();
throw new Error(
`Something went wrong determining wallet type. ${error.message}`,
);
}
const result = (await resp.json()) as
| {
isNewUser: true;
recoveryShareManagement: RecoveryShareManagement;
}
| {
isNewUser: false;
Expand All @@ -118,59 +116,33 @@ export async function generateAuthTokenFromCognitoEmailOtp(
id_token: session.getIdToken().getJwtToken(),
developerClientId: clientId,
otpMethod: "email",
recoveryShareManagement: RecoveryShareManagement.AWS_MANAGED,
}),
});
if (!resp.ok) {
const { error } = await resp.json();
const error = await resp.json();
throw new Error(
`Something went wrong generating auth token from user cognito email otp. ${error}`,
`Something went wrong generating auth token from user cognito email otp. ${error.message}`,
);
}
const respJ = await resp.json();
return respJ as {
verifiedToken: {
rawToken: string;
jwtToken: string;
authProvider: AuthProvider;
developerClientId: string;
authDetails: {
email?: string;
userWalletId: string;
recoveryCode?: string;
cookieString?: string;
recoveryShareManagement: RecoveryShareManagement;
};
authProvider: AuthProvider;
userId: string;
developerClientId: string;
isNewUser: boolean;
};
verifiedTokenJwtString: string;
};
}

export async function initWalletWithoutRecoveryCode({
clientId,
}: {
clientId: string;
}) {
const resp = await authFetchEmbeddedWalletUser(
{ clientId },
ROUTE_INIT_RECOVERY_CODE_FREE_WALLET,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId,
}),
},
);
if (!resp.ok) {
const { error } = await resp.json();
console.error(`Error initializing wallet: ${error} `);
return { success: false };
}

return { success: true };
}

export async function storeUserShares({
clientId,
walletAddress,
Expand Down Expand Up @@ -198,11 +170,13 @@ export async function storeUserShares({
}),
},
);

if (!resp.ok) {
const { error } = await resp.json();
const error = await resp.json();

throw new Error(
`Something went wrong storing user wallet shares: ${JSON.stringify(
error,
error.message,
null,
2,
)}`,
Expand All @@ -219,10 +193,10 @@ export async function getUserShares(clientId: string, getShareUrl: URL) {
},
);
if (!resp.ok) {
const { error } = await resp.json();
const error = await resp.json();
throw new Error(
`Something went wrong getting user's wallet: ${JSON.stringify(
error,
error.message,
null,
2,
)} `,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,17 @@ export const COGNITO_IDENTITY_POOL_ID =
export const GENERATE_RECOVERY_PASSWORD_LAMBDA_FUNCTION =
"arn:aws:lambda:us-west-2:324457261097:function:recovery-share-password-GenerateRecoverySharePassw-bbE5ZbVAToil";

const BASE_URL = "https://ews.thirdweb.com";
const ROUTE_2022_08_12_API_BASE_PATH = `${BASE_URL}/api/2022-08-12`;
const BASE_URL_2023 = "https://embedded-wallet.thirdweb.com/";
const ROUTE_2023_10_20_API_BASE_PATH = `${BASE_URL_2023}api/2023-10-20`;

export const ROUTE_GET_EMBEDDED_WALLET_DETAILS = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/user-wallet-details`;
export const ROUTE_VERIFY_COGNITO_OTP = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/verify-cognito-otp`;
export const ROUTE_GET_EMBEDDED_WALLET_DETAILS = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-user-details`;
export const ROUTE_VERIFY_COGNITO_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-cognito-email-otp`;
export const ROUTE_COGNITO_IDENTITY_POOL_URL = `cognito-idp.${AWS_REGION}.amazonaws.com/${COGNITO_USER_POOL_ID}`;
export const ROUTE_INIT_RECOVERY_CODE_FREE_WALLET = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/init-recovery-code-free-wallet`;
export const ROUTE_STORE_USER_SHARES = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/store-shares`;
export const ROUTE_GET_USER_SHARES = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/get-shares`;
export const ROUTE_VERIFY_THIRDWEB_CLIENT_ID = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/verify-thirdweb-client-id`;
export const ROUTE_AUTH_JWT_CALLBACK = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/jwt-callback`;
export const ROUTE_STORE_USER_SHARES = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-shares`;
export const ROUTE_GET_USER_SHARES = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-shares`;
export const ROUTE_VERIFY_THIRDWEB_CLIENT_ID = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/verify-thirdweb-client-id`;
export const ROUTE_AUTH_JWT_CALLBACK = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-custom-jwt`;

export const ROUTE_HEADLESS_GOOGLE_LOGIN = `${BASE_URL}/api/2022-08-12/embedded-wallet/headless-login-link`;
export const ROUTE_HEADLESS_GOOGLE_LOGIN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/headless-oauth-login-link`;

export const ROUTE_AUTH_COGNITO_ID_TOKEN = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/cognito-id-token`;
export const ROUTE_AUTH_COGNITO_ID_TOKEN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/cognito-id-token`;
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { SetUpWalletRpcReturnType } from "@paperxyz/embedded-wallet-service-sdk";
import { Wallet, utils } from "ethers";
import * as secrets from "secrets.js-34r7h";
import {
initWalletWithoutRecoveryCode,
storeUserShares,
} from "../api/fetchers";
import { storeUserShares } from "../api/fetchers";

import { logoutUser } from "../auth/logout";
import {
Expand All @@ -21,11 +18,6 @@ export async function setUpNewUserWallet(
clientId: string,
) {
try {
// We are using a recovery code override, we mark the wallet as initialized so we don't generate another one the next time
const { success } = await initWalletWithoutRecoveryCode({ clientId });
if (!success) {
throw new Error("Error initializing wallet without recovery code");
}
return await createEmbeddedWallet({
clientId,
recoveryCode: recoveryCode,
Expand All @@ -52,6 +44,7 @@ async function createEmbeddedWallet({
} & SetUpWalletRpcReturnType
> {
const walletDetails = createWalletShares();

const maybeDeviceShare = await storeShares({
clientId,
walletAddress: walletDetails.publicAddress,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {InvokeCommand, LambdaClient} from '@aws-sdk/client-lambda';
import {fromCognitoIdentityPool} from '@aws-sdk/credential-providers';
import { InvokeCommand, LambdaClient } from "@aws-sdk/client-lambda";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-providers";

import {authFetchEmbeddedWalletUser} from '../api/fetchers';
import { authFetchEmbeddedWalletUser } from "../api/fetchers";
import {
AWS_REGION,
COGNITO_IDENTITY_POOL_ID,
GENERATE_RECOVERY_PASSWORD_LAMBDA_FUNCTION,
ROUTE_AUTH_COGNITO_ID_TOKEN,
ROUTE_COGNITO_IDENTITY_POOL_URL,
} from '../constants';
} from "../constants";

export async function getCognitoRecoveryPassword(clientId: string) {
const idTokenResponse = await authFetchEmbeddedWalletUser(
{clientId},
{ clientId },
ROUTE_AUTH_COGNITO_ID_TOKEN,
{
method: 'GET',
method: "GET",
},
);
if (!idTokenResponse.ok) {
Expand All @@ -28,9 +28,7 @@ export async function getCognitoRecoveryPassword(clientId: string) {
);
}
const idTokenResult = await idTokenResponse.json();
const {
data: {id_token: idToken, access_token: accessToken},
} = idTokenResult;
const { idToken, accessToken } = idTokenResult;

const cognitoIdentity = fromCognitoIdentityPool({
clientConfig: {
Expand Down Expand Up @@ -62,9 +60,9 @@ export async function getCognitoRecoveryPassword(clientId: string) {
};
const data = await lambdaClient.send(new InvokeCommand(params));
if (!data.Payload) {
throw new Error('No payload');
throw new Error("No payload");
}
const encKeyResult = JSON.parse(Buffer.from(data.Payload).toString('utf-8'));
const encKeyResult = JSON.parse(Buffer.from(data.Payload).toString("utf-8"));

const result = JSON.parse(encKeyResult.body).recoveryShareEncKey as string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,21 @@ export async function getShares<
const queryParams: Record<string, boolean> = {};
if (authShare.toRetrieve) {
queryParams.getEncryptedAuthShare = true;
} else {
queryParams.getEncryptedAuthShare = false;
}
if (recoveryShare.toRetrieve) {
queryParams.getEncryptedRecoveryShare = true;
if (!recoveryShare.recoveryCode) {
// purposely using a vague name to prevent people from inspecting url from figuring out what it does
// so as to not cause huge debates on the technicality of the custodial // non-custodial
queryParams.useSealedSecret = true;
} else {
queryParams.useSealedSecret = false;
}
} else {
queryParams.getEncryptedRecoveryShare = false;
queryParams.useSealedSecret = false;
}

const getShareUrl = new URL(ROUTE_GET_USER_SHARES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export const AccountRecovery = ({ close, goBack }: EnterPasswordProps) => {
const onNextPress = async () => {};

const onCodeCopyPress = async (code: string) => {
console.log("code", code);

await Clipboard.setStringAsync(code);
setCodeCopied(true);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const EmbeddedSocialConnection: React.FC<
}
})
.catch((error) => {
console.error("Error validating otp: ", error);
console.error("Error logging in with google: ", error);
setErrorMessage("Error login in. Please try again later.");
});
}, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const EnterPassword = ({
setCheckingPass(true);
if (isCreatePassword) {
// Call create password
console.log("password", password);
console.log("[TODO] Implement", password);
} else {
// Call enter password
setErrorMessage("test");
Expand Down