Skip to content

Commit

Permalink
Merge pull-request #176
Browse files Browse the repository at this point in the history
  • Loading branch information
r-n-o committed Nov 28, 2023
2 parents 0c752c0 + 9e648f1 commit bed2bea
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 330 deletions.
3 changes: 2 additions & 1 deletion examples/with-federated-passkeys/.env.local.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
API_PUBLIC_KEY="<Turnkey API Public Key (that starts with 02 or 03)>"
API_PRIVATE_KEY="<Turnkey API Private Key>"
NEXT_PUBLIC_ORGANIZATION_ID="<Turnkey organization ID>"
NEXT_PUBLIC_BASE_URL="https://api.turnkey.com"
NEXT_PUBLIC_BASE_URL="https://api.turnkey.com"
NEXT_PUBLIC_RPID="localhost"
10 changes: 10 additions & 0 deletions examples/with-federated-passkeys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Now open `.env.local` and add the missing environment variables:
- `API_PRIVATE_KEY`
- `NEXT_PUBLIC_ORGANIZATION_ID`
- `NEXT_PUBLIC_BASE_URL` (the `NEXT_PUBLIC` prefix makes the env variable accessible to the frontend app)
- `NEXT_PUBLIC_RPID` should be `localhost` unless you're accessing this demo through your own domain

### 3/ Running the app

Expand All @@ -44,3 +45,12 @@ $ pnpm run dev
```

This command will run a NextJS app on port 3000. If you navigate to http://localhost:3000 in your browser, you can follow the prompts to create a sub-organization and private key for the newly created sub-organization.

### Testing passkey prompts on real mobile devices

The easiest way to test this demo on mobile is through ngrok:

- Install by following the instruction here: https://dashboard.ngrok.com/get-started/setup
- Open a new tunnel to port 3000: `ngrok http 3000`
- Update `NEXT_PUBLIC_RPID` to the ngrok domain (e.g. `372b-68-203-12-187.ngrok-free.app`)
- Now visit the ngrok URL on your mobile device
107 changes: 0 additions & 107 deletions examples/with-federated-passkeys/src/app/globals.css

This file was deleted.

19 changes: 19 additions & 0 deletions examples/with-federated-passkeys/src/app/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type CreateSubOrgResponse = {
subOrgId: string;
wallet: TFormattedWallet;
};

export type GetWalletRequest = {
organizationId: string;
};

export type TFormattedWallet = {
id: string;
name: string;
accounts: TFormattedWalletAccount[];
};

export type TFormattedWalletAccount = {
address: string;
path: string;
};
17 changes: 17 additions & 0 deletions examples/with-federated-passkeys/src/app/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TFormattedWallet } from "./types";

export function refineNonNull<T>(
input: T | null | undefined,
errorMessage?: string
Expand All @@ -8,3 +10,18 @@ export function refineNonNull<T>(

return input;
}

/**
* This function returns the next available BIP 32 path for the wallet
* For example: a wallet with the last address at "m/44'/60'/0'/0/13" will yield "m/44'/60'/0'/0/14"
* @param wallet
*/
export function getNextPath(wallet: TFormattedWallet): string {
const lastAccount = wallet.accounts[wallet.accounts.length - 1];
const lastAccountNum = parseInt(lastAccount.path.split("/")[5]);
return lastAccount.path
.split("/")
.slice(0, 5)
.concat((lastAccountNum + 1).toString())
.join("/");
}
42 changes: 25 additions & 17 deletions examples/with-federated-passkeys/src/pages/api/createSubOrg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createActivityPoller,
} from "@turnkey/http";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
import { CreateSubOrgResponse, TFormattedWallet } from "@/app/types";

type TAttestation = TurnkeyApiTypes["v1Attestation"];

Expand All @@ -14,16 +15,18 @@ type CreateSubOrgRequest = {
attestation: TAttestation;
};

type CreateSubOrgResponse = {
subOrgId: string;
privateKeyId: string;
privateKeyAddress: string;
};

type ErrorMessage = {
message: string;
};

// Default path for the first Ethereum address in a new HD wallet.
// See https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki, paths are in the form:
// m / purpose' / coin_type' / account' / change / address_index
// - Purpose is a constant set to 44' following the BIP43 recommendation.
// - Coin type is set to 60 (ETH) -- see https://github.com/satoshilabs/slips/blob/master/slip-0044.md
// - Account, Change, and Address Index are set to 0
const ETHEREUM_WALLET_DEFAULT_PATH = "m/44'/60'/0'/0/0";

export default async function createUser(
req: NextApiRequest,
res: NextApiResponse<CreateSubOrgResponse | ErrorMessage>
Expand Down Expand Up @@ -72,7 +75,7 @@ export default async function createUser(
{
curve: "CURVE_SECP256K1",
pathFormat: "PATH_FORMAT_BIP32",
path: "m/44'/60'/0'/0/0",
path: ETHEREUM_WALLET_DEFAULT_PATH,
addressFormat: "ADDRESS_FORMAT_ETHEREUM",
},
],
Expand All @@ -81,20 +84,25 @@ export default async function createUser(
});

const subOrgId = refineNonNull(
completedActivity.result.createSubOrganizationResultV3?.subOrganizationId
);
const privateKeys = refineNonNull(
completedActivity.result.createSubOrganizationResultV3?.privateKeys
completedActivity.result.createSubOrganizationResultV4?.subOrganizationId
);
const privateKeyId = refineNonNull(privateKeys?.[0]?.privateKeyId);
const privateKeyAddress = refineNonNull(
privateKeys?.[0]?.addresses?.[0]?.address
const wallet = refineNonNull(
completedActivity.result.createSubOrganizationResultV4?.wallet
);
const walletAddress = wallet.addresses?.[0];

res.status(200).json({
subOrgId,
privateKeyId,
privateKeyAddress,
subOrgId: subOrgId,
wallet: {
id: wallet.walletId,
name: walletName,
accounts: [
{
address: walletAddress,
path: ETHEREUM_WALLET_DEFAULT_PATH,
},
],
},
});
} catch (e) {
console.error(e);
Expand Down
72 changes: 0 additions & 72 deletions examples/with-federated-passkeys/src/pages/api/getPrivateKeys.ts

This file was deleted.

59 changes: 59 additions & 0 deletions examples/with-federated-passkeys/src/pages/api/getWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { TurnkeyApiTypes, TurnkeyClient } from "@turnkey/http";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
import { GetWalletRequest, TFormattedWallet } from "@/app/types";

type TWalletAccount = TurnkeyApiTypes["v1WalletAccount"];

type ErrorMessage = {
message: string;
};

// This can be performed by the parent org since parent orgs have read-only access to all their sub-orgs
export default async function getWallet(
req: NextApiRequest,
res: NextApiResponse<TFormattedWallet | ErrorMessage>
) {
const getWalletRequest = req.body as GetWalletRequest;

const turnkeyClient = new TurnkeyClient(
{ baseUrl: process.env.NEXT_PUBLIC_BASE_URL! },
new ApiKeyStamper({
apiPublicKey: process.env.API_PUBLIC_KEY!,
apiPrivateKey: process.env.API_PRIVATE_KEY!,
})
);

const organizationId = getWalletRequest.organizationId;

try {
const walletsResponse = await turnkeyClient.getWallets({
organizationId,
});
const accountsResponse = await turnkeyClient.getWalletAccounts({
organizationId: organizationId,
walletId: walletsResponse.wallets[0].walletId,
});

const accounts = accountsResponse.accounts.map((acc: TWalletAccount) => {
return {
address: acc.address,
path: acc.path,
};
});

res.status(200).json({
id: walletsResponse.wallets[0].walletId,
name: walletsResponse.wallets[0].walletName,
accounts: accounts,
});
} catch (e) {
console.error(e);

res.status(500).json({
message: "Something went wrong.",
});
}

res.json;
}
Loading

0 comments on commit bed2bea

Please sign in to comment.