Skip to content

Instantly share code, notes, and snippets.

@AlmostEfficient
Last active April 20, 2025 11:42
Show Gist options
  • Save AlmostEfficient/fb0ea2e867a7b3e48caff69ea008c5fd to your computer and use it in GitHub Desktop.
Save AlmostEfficient/fb0ea2e867a7b3e48caff69ea008c5fd to your computer and use it in GitHub Desktop.
express server service to create and fetch turnkey solana wallets
import { Turnkey } from '@turnkey/sdk-server';
import { logger } from '../utils/logger.js';
import fetch from 'node-fetch';
const turnkey = new Turnkey({
apiBaseUrl: process.env.TURNKEY_API_BASE_URL || 'https://api.turnkey.com',
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
});
/**
* Create a sub-organization with a Solana wallet for a user
* @param {string} email - User's email
* @returns {Promise<Object>} - Sub-organization and wallet details
*/
export const createSubOrganization = async (email) => {
try {
const apiClient = turnkey.apiClient();
const subOrganizationConfig = {
subOrganizationName: `${email}'s Forma Wallet`,
rootUsers: [{
userName: email,
userEmail: email,
apiKeys: [],
authenticators: [],
oauthProviders: []
}],
rootQuorumThreshold: 1,
wallet: {
walletName: "Forma Solana Wallet",
accounts: [
{
curve: "CURVE_ED25519", // Solana uses Ed25519
pathFormat: "PATH_FORMAT_BIP32",
path: "m/44'/501'/0'/0'", // Solana's BIP44 path
addressFormat: "ADDRESS_FORMAT_SOLANA"
}
]
}
};
logger.info(`Creating sub-organization for email: ${email}`);
const response = await apiClient.createSubOrganization(subOrganizationConfig);
logger.info(`Sub-organization created with ID: ${response.subOrganizationId}`);
return {
subOrganizationId: response.subOrganizationId,
wallet: {
walletId: response.wallet.walletId,
addresses: response.wallet.addresses
}
};
} catch (error) {
logger.error(`Error creating sub-organization: ${error.message}`);
throw error;
}
};
/**
* Get sub-organizations and wallet addresses for a user by email
* @param {string} email - User's email
* @returns {Promise<Array>} - List of sub-organizations and their wallets
*/
export const getUserSubOrganizations = async (email) => {
try {
logger.info(`Fetching organizations for email: ${email}`);
const apiClient = turnkey.apiClient();
const response = await apiClient.getSubOrgIds({
organizationId: process.env.TURNKEY_ORGANIZATION_ID,
filterType: "EMAIL",
filterValue: email
});
console.log('getSubOrgIds response:', JSON.stringify(response, null, 2));
const userSubOrgs = [];
for (const orgId of response.organizationIds) {
console.log(`\nFetching wallets for organization: ${orgId}`);
const walletsResponse = await apiClient.getWallets({
organizationId: orgId
});
console.log('getWallets response:', JSON.stringify(walletsResponse, null, 2));
const wallets = walletsResponse.wallets.map(wallet => ({
walletId: wallet.walletId,
walletName: wallet.walletName,
addresses: wallet.addresses
}));
console.log('Processed wallet data:', JSON.stringify(wallets, null, 2));
userSubOrgs.push({
subOrganizationId: orgId,
wallets
});
}
console.log('Final response:', JSON.stringify(userSubOrgs, null, 2));
return userSubOrgs;
} catch (error) {
console.error('Detailed error:', error);
logger.error(`Error fetching organizations: ${error.message}`);
throw error;
}
};
/**
* Gathers comprehensive debug information for a user by email.
* Logs details about the user, their sub-organizations, wallets, and accounts.
* @param {string} email - User's email
* @returns {Promise<void>}
*/
export const debugUserInfo = async (email) => {
const apiClient = turnkey.apiClient();
const rootOrganizationId = process.env.TURNKEY_ORGANIZATION_ID;
console.log(`\n--- Debugging user: ${email} ---`);
console.log(`--- Using Organization ID: ${rootOrganizationId} ---`);
// Helper to log errors
const logError = (context, error) => {
console.error(`[${context}] Error:`, error?.message || error);
if (error?.details) {
console.error(`[${context}] Error Details:`, JSON.stringify(error.details, null, 2));
}
};
try {
// 1. Get User details in the root organization
console.log(`\n[Root Org: ${rootOrganizationId}] Fetching users...`);
const rootUsers = await apiClient.getUsers({
organizationId: rootOrganizationId,
});
console.log(`[Root Org: ${rootOrganizationId}] getUsers response:`, JSON.stringify(rootUsers, null, 2));
const matchingRootUsers = rootUsers.users.filter(user => user.userEmail === email);
if (matchingRootUsers.length > 0) {
console.log(`[Root Org: ${rootOrganizationId}] Found matching user(s) in root org:`, JSON.stringify(matchingRootUsers, null, 2));
} else {
console.log(`[Root Org: ${rootOrganizationId}] No user found with email ${email} in root org.`);
}
} catch (error) {
logError(`Root Org: ${rootOrganizationId} - Fetching Users`, error);
}
let subOrgIds = [];
try {
// 2. Get Sub-Organization IDs
console.log(`\n[Org ID: ${rootOrganizationId}] Fetching sub-org IDs for email: ${email}`);
const subOrgResponse = await apiClient.getSubOrgIds({
organizationId: rootOrganizationId,
filterType: "EMAIL",
filterValue: email
});
console.log(`[Org ID: ${rootOrganizationId}] getSubOrgIds response:`, JSON.stringify(subOrgResponse, null, 2));
subOrgIds = subOrgResponse.organizationIds || [];
if (subOrgIds.length > 0) {
console.log(`[Org ID: ${rootOrganizationId}] Found ${subOrgIds.length} sub-organization(s).`);
} else {
console.log(`[Org ID: ${rootOrganizationId}] No sub-organizations found.`);
}
} catch (error) {
logError(`Fetching Sub-Org IDs`, error);
}
// 3. Iterate through found sub-organizations
for (const subOrgId of subOrgIds) {
console.log(`\n--- Processing Sub-Organization ID: ${subOrgId} ---`);
try {
// 3a. Get Sub-Organization details
console.log(`[Sub Org: ${subOrgId}] Fetching organization details...`);
const orgDetails = await apiClient.getOrganization({ organizationId: subOrgId });
console.log(`[Sub Org: ${subOrgId}] getOrganization response:`, JSON.stringify(orgDetails, null, 2));
} catch (error) {
logError(`Sub Org: ${subOrgId} - Fetching Details`, error);
}
try {
// 3b. Get Users within the sub-organization
console.log(`[Sub Org: ${subOrgId}] Fetching users...`);
const subOrgUsers = await apiClient.getUsers({ organizationId: subOrgId });
console.log(`[Sub Org: ${subOrgId}] getUsers response:`, JSON.stringify(subOrgUsers, null, 2));
const matchingSubOrgUsers = subOrgUsers.users.filter(user => user.userEmail === email);
if (matchingSubOrgUsers.length > 0) {
console.log(`[Sub Org: ${subOrgId}] Found matching user(s):`, JSON.stringify(matchingSubOrgUsers, null, 2));
} else {
console.log(`[Sub Org: ${subOrgId}] No user found with email ${email} in this sub-org.`);
}
} catch (error) {
logError(`Sub Org: ${subOrgId} - Fetching Users`, error);
}
let walletIds = [];
try {
// 3c. Get Wallets within the sub-organization
console.log(`[Sub Org: ${subOrgId}] Fetching wallets...`);
const walletsResponse = await apiClient.getWallets({ organizationId: subOrgId });
console.log(`[Sub Org: ${subOrgId}] getWallets response:`, JSON.stringify(walletsResponse, null, 2));
walletIds = walletsResponse.wallets.map(w => w.walletId);
if (walletIds.length === 0) {
console.log(`[Sub Org: ${subOrgId}] No wallets found in this sub-organization.`);
}
} catch (error) {
logError(`Sub Org: ${subOrgId} - Fetching Wallets`, error);
}
// 4. Iterate through found wallets
for (const walletId of walletIds) {
console.log(`\n--- [Sub Org: ${subOrgId}] Processing Wallet ID: ${walletId} ---`);
try {
// 4a. Get specific Wallet details
console.log(`[Wallet: ${walletId}] Fetching wallet details...`);
const walletDetails = await apiClient.getWallet({ organizationId: subOrgId, walletId: walletId });
console.log(`[Wallet: ${walletId}] getWallet response:`, JSON.stringify(walletDetails, null, 2));
} catch (error) {
logError(`Wallet: ${walletId} - Fetching Details`, error);
}
try {
// 4b. Get Wallet Accounts
console.log(`[Wallet: ${walletId}] Fetching wallet accounts...`);
const walletAccounts = await apiClient.getWalletAccounts({ organizationId: subOrgId, walletId: walletId });
console.log(`[Wallet: ${walletId}] getWalletAccounts response:`, JSON.stringify(walletAccounts, null, 2));
if (!walletAccounts.accounts || walletAccounts.accounts.length === 0) {
console.log(`[Wallet: ${walletId}] No accounts found in this wallet.`);
}
} catch (error) {
logError(`Wallet: ${walletId} - Fetching Accounts`, error);
}
}
}
console.log(`\n--- Debugging finished for user: ${email} ---`);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment