Skip to content
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

Simplifying delegated access steps #265

Merged
merged 4 commits into from
Apr 8, 2025

Conversation

mintlify[bot]
Copy link
Contributor

@mintlify mintlify bot commented Apr 2, 2025

Surface changes to improve clarify of delegated access setup

@hmarnold hmarnold requested review from t-vila and BryceTurnkey April 2, 2025 23:52
@@ -67,9 +83,7 @@ To set up delegated access, here’s one example of how you can create a limited
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could maybe insert here a code example of how to create such a sub-org on the server side with an email only end user:


import { Turnkey } from "@turnkey/sdk-server";
import dotenv from "dotenv";

dotenv.config();

// Initialize the Turnkey Server Client on the server-side
const turnkeyServer = new Turnkey({
    apiBaseUrl: "https://api.turnkey.com",
    apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
    apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
    defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
  }).apiClient();


  // To create an API key programatically check https://github.com/tkhq/sdk/blob/main/examples/kitchen-sink/src/sdk-server/createApiKey.ts
  const publicKey = "<delegator_api_public_key>";
  const curveType = "API_KEY_CURVE_P256"; // this is the default
  const apiKeys = [
    {
      apiKeyName: "Delegated - API Key",
      publicKey,
      curveType,
    },
  ];

  const subOrg = await turnkeyServer.createSubOrganization({
    organizationId: process.env.TURNKEY_ORGANIZATION_ID,
    subOrganizationName: `Sub Org - With Delegator`,
    rootUsers: [
      {
        userName: "Delegator User",
        userEmail: "...",
        apiKeys,
        authenticators: [],
        oauthProviders: []
      },
      {
          userName: "End User",
          userEmail: "...",
          apiKeys: [],
          authenticators: [],
          oauthProviders: []
      },
    ],
    rootQuorumThreshold: 1,
    wallet: {
      "walletName": "Default ETH Wallet",
      "accounts": [
        {
          "curve": "CURVE_SECP256K1",
          "pathFormat": "PATH_FORMAT_BIP32",
          "path": "m/44'/60'/0'/0/0",
          "addressFormat": "ADDRESS_FORMAT_ETHEREUM"
        }
      ]
    },
  });

  console.log("sub-org id:", subOrg.subOrganizationId);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be totally valuable to add in!

@@ -113,12 +110,10 @@ Here’s one example, creating a Delegated Account that only has permission to s
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could reference this example on how to create a policy on the server side:

https://github.com/tkhq/sdk/blob/main/examples/kitchen-sink/src/sdk-server/createPolicy.ts

And for clarity mention what should be the api key and org id:
API_PUBLIC_KEY / API_PRIVATE_KEY -> this is your delegator account api key added in the sub-organization above
ORGANIZATION_ID -> this is your newly created sub-organization id

* Create a new policy that explicitly grants the Service Account permission to delete users. This is necessary because, once removed from the Root Quorum, the Service Account will have no permissions by default.
* [Update the root quorum](/api-reference/organizations/update-root-quorum) to remove the Service Account from the root quorum
* Delete the Service Account user from the organization
* [Update the root quorum](/api-reference/organizations/update-root-quorum) to remove the Delegated Account from the root quorum

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to update the root quorum (same notes about the api key and org id as for the create policy above):

https://github.com/tkhq/sdk/blob/main/examples/kitchen-sink/src/sdk-server/updateRootQuorum.ts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should just put together an example in the sdk repo for this use-case and link it at the top? Or if you think its concise enough the code could just be pasted all here in one block?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zkharit here's the full thing - mind having a look pls:

import { Turnkey } from "@turnkey/sdk-server";
import dotenv from "dotenv";

dotenv.config();

async function main() {
// Initialize a Turnkey client using the parent organization
// used to create the sub-organization with the two users in it
const turnkeyClient = new Turnkey({
    apiBaseUrl: "https://api.turnkey.com",
    apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
    apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
    defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
  }).apiClient();

// for extra clarity, using a separate API key than the one used by the parent org
// make sure to have both DELEGATOR_API_PUBLIC_KEY and DELEGATOR_API_PRIVATE_KEY added in .env
  const curveType = "API_KEY_CURVE_P256";
  const apiKeys = [
    {
      apiKeyName: "Delegated - API Key",
      publicKey: process.env.DELEGATOR_API_PUBLIC_KEY,
      curveType,
    },
  ];

  const subOrg = await turnkeyClient.createSubOrganization({
    organizationId: process.env.TURNKEY_ORGANIZATION_ID,
    subOrganizationName: `Sub Org - With Delegator`,
    rootUsers: [
      {
        userName: "Delegator User",
        userEmail: "[email protected]",
        apiKeys,
        authenticators: [],
        oauthProviders: []
      },
      {
          userName: "End User",
          userEmail: "[email protected]",
          apiKeys: [],
          authenticators: [],
          oauthProviders: []
      },
    ],
    rootQuorumThreshold: 1,
    wallet: {
      "walletName": "Default ETH Wallet",
      "accounts": [
        {
          "curve": "CURVE_SECP256K1",
          "pathFormat": "PATH_FORMAT_BIP32",
          "path": "m/44'/60'/0'/0/0",
          "addressFormat": "ADDRESS_FORMAT_ETHEREUM"
        }
      ]
    },
  });

  console.log("sub-org id:", subOrg.subOrganizationId);

  // Initializing the Turkey client used by the Delegator activities
  // Notice the subOrganizationId created above 
  const turnkeyDelegator = new Turnkey({
    apiBaseUrl: "https://api.turnkey.com",
    apiPrivateKey: process.env.DELEGATOR_API_PRIVATE_KEY,
    apiPublicKey: process.env.DELEGATOR_API_PUBLIC_KEY,
    defaultOrganizationId: subOrg.subOrganizationId,
  }).apiClient();

  // Creating a policy for the Delegated account 
  const delegator_userid = subOrg.rootUserIds[0];
  const policyName = "Allow Delegated Account to sign transactions to specific address";
  const effect = "EFFECT_ALLOW";
  const consensus = `approvers.any(user, user.id == '${delegator_userid}')`;
  const condition = `eth.tx.to == '${process.env.RECIPIENT_ADDRESS}'`;
  const notes = "";

  const { policyId } = await turnkeyDelegator.createPolicy({
    policyName,
    condition,
    consensus,
    effect,
    notes,
  });

  console.log(
    [
      `New policy created!`,
      `- Name: ${policyName}`,
      `- Policy ID: ${policyId}`,
      `- Effect: ${effect}`,
      `- Consensus: ${consensus}`,
      `- Condition: ${condition}`,
      ``,
    ].join("\n"),
  );

  // Remove the Delegated Account from the root quorum
  const RootQuorum = await turnkeyDelegator.updateRootQuorum({
    threshold: 1,
    userIds: [subOrg.rootUserIds[1]], // retain the end user
  });

  console.log("Root Quorum updated! :", RootQuorum);

}
  main().catch((error) => {
    console.error(error);
    process.exit(1);
  });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome, might want to use <user-email-addres> or something similar for the email address, but this is a great snippet here!

@omkarshanbhag omkarshanbhag merged commit 9a25f85 into main Apr 8, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants