Skip to content

Instantly share code, notes, and snippets.

@AlmostEfficient
Created March 19, 2025 06:45
Show Gist options
  • Save AlmostEfficient/8aae477125dd253413bdd1ee79731adc to your computer and use it in GitHub Desktop.
Save AlmostEfficient/8aae477125dd253413bdd1ee79731adc to your computer and use it in GitHub Desktop.
Create a non-custodial Solana wallet on Turnkey with a Supabase edge function
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { Turnkey } from "npm:@turnkey/sdk-server";
import { corsHeaders } from "../_shared/cors.ts";
const turnkey = new Turnkey({
apiBaseUrl: "https://api.turnkey.com",
apiPrivateKey: Deno.env.get("TURNKEY_API_PRIVATE_KEY")!,
apiPublicKey: Deno.env.get("TURNKEY_API_PUBLIC_KEY")!,
defaultOrganizationId: Deno.env.get("TURNKEY_ORGANIZATION_ID")!,
});
type CreateSubOrgResponse = {
subOrganizationId: string;
wallet: {
walletId: string;
addresses: string[];
};
};
async function createSubOrganization(email: string): Promise<CreateSubOrgResponse> {
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"
}
]
}
};
const response = await apiClient.createSubOrganization(subOrganizationConfig);
return {
subOrganizationId: response.subOrganizationId,
wallet: {
walletId: response.wallet.walletId,
addresses: response.wallet.addresses
}
};
}
serve(async (req) => {
// Handle CORS
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
// Step 1: Parse and validate request
const { email } = await req.json() as { email: string };
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
throw new Error("Invalid email format");
}
console.log('✓ Email validation passed:', email)
// Step 2: Test connection by getting organization ID
const organizationId = Deno.env.get("TURNKEY_ORGANIZATION_ID");
if (!organizationId) {
throw new Error("TURNKEY_ORGANIZATION_ID not set");
}
console.log('✓ Turnkey client initialized with organization:', organizationId)
// Create sub-organization for the user
const subOrgResult = await createSubOrganization(email);
console.log('✓ Created sub-organization:', subOrgResult.subOrganizationId);
// Return success response
return new Response(
JSON.stringify({
success: true,
message: "Sub-organization created successfully",
email,
organizationId,
subOrganizationId: subOrgResult.subOrganizationId,
walletId: subOrgResult.wallet.walletId,
addresses: subOrgResult.wallet.addresses
}),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json'
},
status: 200
}
);
} catch (error) {
console.error('Error:', error.message)
return new Response(
JSON.stringify({
success: false,
error: error.message
}),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json'
},
status: error.message.includes("Invalid email") ? 400 : 500
}
);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment