Skip to content

[SDK]: add backend wallets for in app wallet #5878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 8, 2025
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: 23 additions & 0 deletions .changeset/popular-ravens-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"thirdweb": minor
---

Add support for backend wallets.

This is useful is you have a backend that is connected to an that you want to have programmatic access to a wallet without managing private keys.

Here's how you'd do it:

```typescript
const wallet = inAppWallet();
const account = await wallet.connect({
strategy: "backend",
client: createThirdwebClient({
secretKey: "...",
}),
walletSecret: "...",
});
console.log("account.address", account.address);
```

Note that `walletSecret` should be generated by you and securely stored to uniquely authenticate to the given wallet.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, expect, it, vi } from "vitest";
import { TEST_CLIENT } from "~test/test-clients.js";
import { getClientFetch } from "../../../../utils/fetch.js";
import { stringify } from "../../../../utils/json.js";
import { backendAuthenticate } from "./backend.js";
import { getLoginUrl } from "./getLoginPath.js";

// Mock dependencies
vi.mock("../../../../utils/fetch.js");
vi.mock("./getLoginPath.js");

describe("backendAuthenticate", () => {
it("should successfully authenticate and return token", async () => {
// Mock response data
const mockResponse = {
token: "mock-token",
cookieString: "mock-cookie",
};

// Mock fetch implementation
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockResponse),
});

// Mock dependencies
vi.mocked(getClientFetch).mockReturnValue(mockFetch);
vi.mocked(getLoginUrl).mockReturnValue("/auth/login");

const result = await backendAuthenticate({
client: TEST_CLIENT,
walletSecret: "test-secret",
});

// Verify the fetch call
expect(mockFetch).toHaveBeenCalledWith("/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: stringify({ walletSecret: "test-secret" }),
});

// Verify response
expect(result).toEqual(mockResponse);
});

it("should throw error when authentication fails", async () => {
// Mock failed fetch response
const mockFetch = vi.fn().mockResolvedValue({
ok: false,
});

// Mock dependencies
vi.mocked(getClientFetch).mockReturnValue(mockFetch);
vi.mocked(getLoginUrl).mockReturnValue("/auth/login");

// Test inputs
const args = {
client: TEST_CLIENT,
walletSecret: "test-secret",
};

// Verify error is thrown
await expect(backendAuthenticate(args)).rejects.toThrow(
"Failed to generate backend account",
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ThirdwebClient } from "../../../../client/client.js";
import { getClientFetch } from "../../../../utils/fetch.js";
import { stringify } from "../../../../utils/json.js";
import type { Ecosystem } from "../wallet/types.js";
import { getLoginUrl } from "./getLoginPath.js";
import type { AuthStoredTokenWithCookieReturnType } from "./types.js";

/**
* Authenticates via the wallet secret
* @internal
*/
export async function backendAuthenticate(args: {
client: ThirdwebClient;
walletSecret: string;
ecosystem?: Ecosystem;
}): Promise<AuthStoredTokenWithCookieReturnType> {
const clientFetch = getClientFetch(args.client, args.ecosystem);
const path = getLoginUrl({
authOption: "backend",
client: args.client,
ecosystem: args.ecosystem,
});
const res = await clientFetch(`${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: stringify({
walletSecret: args.walletSecret,
}),
});

if (!res.ok) throw new Error("Failed to generate backend account");

return (await res.json()) satisfies AuthStoredTokenWithCookieReturnType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ export type SingleStepAuthArgsType =
}
| {
strategy: "guest";
client: ThirdwebClient;
}
| {
strategy: "backend";
walletSecret: string;
};

export type AuthArgsType = (MultiStepAuthArgsType | SingleStepAuthArgsType) & {
Expand All @@ -89,6 +92,7 @@ type RecoveryShareManagement = "USER_MANAGED" | "AWS_MANAGED" | "ENCLAVE";
export type AuthProvider =
| "Cognito"
| "Guest"
| "Backend"
| "Google"
| "EmailOtp"
| "CustomJWT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export async function getOrCreateInAppWalletConnector(
connectorFactory: (client: ThirdwebClient) => Promise<InAppConnector>,
ecosystem?: Ecosystem,
) {
const key = stringify({ clientId: client.clientId, ecosystem });
const key = stringify({
clientId: client.clientId,
ecosystem,
partialSecretKey: client.secretKey?.slice(0, 5),
});
if (connectorCache.has(key)) {
return connectorCache.get(key) as InAppConnector;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/thirdweb/src/wallets/in-app/native/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ export async function preAuthenticate(args: PreAuthArgsType) {
* verificationCode: "123456",
* });
* ```
*
* Authenticate to a backend account (only do this on your backend):
* ```ts
* import { authenticate } from "thirdweb/wallets/in-app";
*
* const result = await authenticate({
* client,
* strategy: "backend",
* walletSecret: "...", // Provided by your app
* });
* ```
* @wallet
*/
export async function authenticate(args: AuthArgsType) {
Expand Down
13 changes: 13 additions & 0 deletions packages/thirdweb/src/wallets/in-app/native/native-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { nativeLocalStorage } from "../../../utils/storage/nativeStorage.js";
import type { Account } from "../../interfaces/wallet.js";
import { getUserStatus } from "../core/actions/get-enclave-user-status.js";
import { authEndpoint } from "../core/authentication/authEndpoint.js";
import { backendAuthenticate } from "../core/authentication/backend.js";
import { ClientScopedStorage } from "../core/authentication/client-scoped-storage.js";
import { guestAuthenticate } from "../core/authentication/guest.js";
import { customJwt } from "../core/authentication/jwt.js";
Expand Down Expand Up @@ -171,6 +172,13 @@ export class InAppNativeConnector implements InAppConnector {
storage: nativeLocalStorage,
});
}
case "backend": {
return backendAuthenticate({
client: this.client,
walletSecret: params.walletSecret,
ecosystem: params.ecosystem,
});
}
case "wallet": {
return siweAuthenticate({
client: this.client,
Expand All @@ -179,6 +187,11 @@ export class InAppNativeConnector implements InAppConnector {
ecosystem: params.ecosystem,
});
}
case "github":
case "twitch":
case "steam":
case "farcaster":
case "telegram":
case "google":
case "facebook":
case "discord":
Expand Down
14 changes: 14 additions & 0 deletions packages/thirdweb/src/wallets/in-app/web/in-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,20 @@ import { createInAppWallet } from "../core/wallet/in-app-core.js";
* });
* ```
*
* ### Connect to a backend account
*
* ```ts
* import { inAppWallet } from "thirdweb/wallets";
*
* const wallet = inAppWallet();
*
* const account = await wallet.connect({
* client,
* strategy: "backend",
* walletSecret: "...", // Provided by your app
* });
* ```
*
* ### Connect with custom JWT (any OIDC provider)
*
* You can use any OIDC provider to authenticate your users. Make sure to configure it in your dashboard under in-app wallet settings.
Expand Down
11 changes: 11 additions & 0 deletions packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ export async function preAuthenticate(args: PreAuthArgsType) {
* verificationCode: "123456",
* });
* ```
*
* Authenticate to a backend account (only do this on your backend):
* ```ts
* import { authenticate } from "thirdweb/wallets/in-app";
*
* const result = await authenticate({
* client,
* strategy: "backend",
* walletSecret: "...", // Provided by your app
* });
* ```
* @wallet
*/
export async function authenticate(args: AuthArgsType) {
Expand Down
Loading
Loading