@osiris-ai/turnkey-sdk
Version:
Osiris Turnkey SDK
395 lines (390 loc) • 14.1 kB
JavaScript
import { ApiKeyStamper } from '@turnkey/api-key-stamper';
import axios from 'axios';
import { WalletAuthenticator, AuthenticatorType } from '@osiris-ai/sdk';
// @osiris-ai/turnkey-sdk - Turnkey Integration SDK
// src/types.ts
var getChainIdFromAddressFormat = (addressFormat) => {
switch (addressFormat) {
case "ADDRESS_FORMAT_ETHEREUM":
return "evm:eip155:1";
case "ADDRESS_FORMAT_SOLANA":
return "solana:mainnet-beta";
default:
return "evm:eip155:1";
}
};
var TurnkeyAuthenticator = class extends WalletAuthenticator {
sdk;
constructor(config) {
super("turnkey", config);
this.sdk = new TurnkeySDK(config);
}
async getWallet(params) {
const walletAccountsResponse = await this.sdk.listWalletAccounts({
organizationId: params.id,
walletId: params.accountId,
paginationOptions: {
limit: "100",
before: "",
after: ""
}
});
const account = {
id: params.id,
addresses: walletAccountsResponse.accounts.map((account2) => ({
chains: [getChainIdFromAddressFormat(account2.addressFormat)],
address: account2.address,
derivationPath: account2.path,
curve: account2.curve,
addressFormat: account2.addressFormat
})),
chains: [getChainIdFromAddressFormat(walletAccountsResponse.accounts[0].addressFormat)],
mnemonicLength: 24
};
return {
id: params.accountId,
name: "",
accounts: account,
authenticators: []
};
}
async addAccount(params) {
const addAccountsResponse = await this.sdk.addWalletAccount({
type: "ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS",
timestampMs: Date.now().toString(),
organizationId: params.walletId,
parameters: {
walletId: params.accountId,
accounts: params.addresses.map((acc) => ({
pathFormat: acc.pathFormat,
path: acc.path,
curve: acc.curve,
addressFormat: acc.addressFormat
}))
}
});
if (!addAccountsResponse) {
throw new Error("Failed to add account");
}
return this.getWallet({
id: params.walletId,
accountId: params.accountId
});
}
async createWallet(params) {
const turnkeyRequest = {
subOrganizationName: params.name,
rootUsers: [
{
userName: params.name,
apiKeys: params.authenticators.filter((authenticator) => authenticator.type === AuthenticatorType.API_KEY).map((authenticator) => ({
apiKeyName: authenticator.credentials.apiKeyName,
publicKey: authenticator.credentials.publicKey,
curveType: authenticator.credentials.curveType
})) ?? [],
authenticators: params.authenticators.filter((authenticator) => authenticator.type === AuthenticatorType.WEBAUTHN).map((authenticator) => ({
authenticatorName: authenticator.credentials.authenticatorName,
challenge: authenticator.credentials.challenge,
attestation: authenticator.credentials.attestation
})) ?? [],
oauthProviders: params.authenticators.filter((authenticator) => authenticator.type === AuthenticatorType.OAUTH).map((authenticator) => ({
oauthProviderName: authenticator.credentials.oauthProviderName,
oidcToken: authenticator.credentials.oidcToken
})) ?? []
}
],
rootQuorumThreshold: 1,
wallet: {
walletName: params.name,
accounts: params.accounts.map((acc) => ({
pathFormat: acc.pathFormat,
path: acc.path,
curve: acc.curve,
addressFormat: acc.addressFormat
})),
mnemonicLength: 12
}
};
const response = await this.sdk.createWallet(turnkeyRequest);
const responseAddresses = response.activity.result.createSubOrganizationResultV7.wallet.addresses;
const allChains = Array.from(new Set(params.accounts.flatMap((acc) => acc.chains)));
const addresses = params.accounts.map((acc, idx) => ({
chains: acc.chains,
address: responseAddresses && responseAddresses[idx] ? responseAddresses[idx] : "",
derivationPath: acc.path,
curve: acc.curve,
addressFormat: acc.addressFormat
}));
const account = {
id: response.activity.result.createSubOrganizationResultV7.wallet.walletId,
addresses,
chains: allChains,
mnemonicLength: 24
};
return {
id: response.activity.result.createSubOrganizationResultV7.subOrganizationId,
name: params.name,
accounts: account,
authenticators: []
// TODO: Not available from Turnkey
};
}
async action(params) {
if (!params.data) {
throw new Error("No data provided");
}
const { method, walletId, walletAddress, payload, chain } = params.data;
if (!method || !walletId || !walletAddress || !payload) {
throw new Error(`Invalid parameters, missing ${!method ? "method" : !walletId ? "walletId" : !walletAddress ? "walletAddress" : "payload"}`);
}
try {
switch (method) {
case "signMessage":
return await this.handleSignMessage(walletId, walletAddress, payload);
case "signTypedData":
return await this.handleSignTypedData(walletId, walletAddress, payload);
case "signTransaction":
return await this.handleSignTransaction(
walletId,
walletAddress,
payload,
chain
);
case "signRawPayloads":
return await this.handleSignRawPayloads(walletId, walletAddress, payload);
default:
throw new Error(`Unsupported method: ${method}`);
}
} catch (error) {
console.error(`Action execution failed:`, error.message);
throw new Error(`Failed to execute ${method}: ${error.response ? JSON.stringify(error.response.data) : error.message}`);
}
}
async handleSignMessage(walletId, walletAddress, payload) {
const tx = await this.sdk.signRawPayloads({
organizationId: walletId,
timestampMs: Date.now().toString(),
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOADS",
parameters: {
signWith: walletAddress,
payloads: Array.isArray(payload) ? payload : [payload],
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NO_OP"
}
});
if (tx.activity.status === "ACTIVITY_STATUS_COMPLETED") {
return {
data: tx.activity.result.signRawPayloadsResult,
status: 200,
statusText: "OK"
};
}
throw new Error("Failed to sign message");
}
async handleSignTypedData(walletId, walletAddress, payload) {
let typedData = payload;
if (Array.isArray(payload)) {
if (payload.length === 0) {
throw new Error("Empty payload array provided for typed data signing");
}
typedData = payload[0];
}
if (!typedData.domain || !typedData.types || !typedData.primaryType || !typedData.message) {
throw new Error("Invalid EIP-712 typed data structure: missing required fields (domain, types, primaryType, message)");
}
const typedDataString = JSON.stringify(typedData);
const tx = await this.sdk.signRawPayload({
organizationId: walletId,
timestampMs: Date.now().toString(),
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
parameters: {
signWith: walletAddress,
payload: typedDataString,
encoding: "PAYLOAD_ENCODING_EIP712",
hashFunction: "HASH_FUNCTION_NO_OP"
}
});
if (tx.activity.status === "ACTIVITY_STATUS_COMPLETED") {
return {
data: {
signatures: [tx.activity.result.signRawPayloadResult],
originalTypedData: typedData
},
status: 200,
statusText: "OK"
};
}
throw new Error("Failed to sign typed data");
}
async handleSignTransaction(walletId, walletAddress, payload, chain) {
if (!payload.serializedTransaction) {
throw new Error("Missing serializedTransaction in payload");
}
const transactionType = chain?.startsWith("evm") ? "TRANSACTION_TYPE_ETHEREUM" : "TRANSACTION_TYPE_SOLANA";
const tx = await this.sdk.signTransaction({
type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
timestampMs: Date.now().toString(),
organizationId: walletId,
parameters: {
signWith: walletAddress,
unsignedTransaction: payload.serializedTransaction,
type: transactionType
}
});
if (tx.activity.status === "ACTIVITY_STATUS_COMPLETED") {
return {
data: tx.activity.result.signTransactionResult,
status: 200,
statusText: "OK"
};
}
throw new Error("Failed to sign transaction");
}
async handleSignRawPayloads(walletId, walletAddress, payload) {
const payloads = Array.isArray(payload) ? payload : [payload];
const tx = await this.sdk.signRawPayloads({
organizationId: walletId,
timestampMs: Date.now().toString(),
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOADS",
parameters: {
signWith: walletAddress,
payloads,
encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
hashFunction: "HASH_FUNCTION_NO_OP"
}
});
if (tx.activity.status === "ACTIVITY_STATUS_COMPLETED") {
return {
data: tx.activity.result.signRawPayloadsResult,
status: 200,
statusText: "OK"
};
}
throw new Error("Failed to sign raw payloads");
}
};
// src/index.ts
var TurnkeySDK = class {
constructor(config) {
this.config = config;
this.stamper = new ApiKeyStamper({
apiPublicKey: this.config.apiPublicKey,
apiPrivateKey: this.config.apiPrivateKey
});
}
stamper;
async listWallets(request) {
const requestBody = request;
const stamp = await this.stamper.stamp(JSON.stringify(requestBody));
const response = await axios.post(
`${this.config.baseUrl}/public/v1/query/list_wallets`,
requestBody,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
}
async listWalletAccounts(request) {
const requestBody = request;
const stamp = await this.stamper.stamp(JSON.stringify(requestBody));
const response = await axios.post(
`${this.config.baseUrl}/public/v1/query/list_wallet_accounts`,
requestBody,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
}
async createWallet(request) {
const requestBody = {
type: "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7",
timestampMs: (/* @__PURE__ */ new Date()).getTime().toString(),
organizationId: this.config.organizationId,
parameters: {
subOrganizationName: request.subOrganizationName,
rootUsers: request.rootUsers.map((rootUser) => ({
userName: rootUser.userName,
apiKeys: rootUser.apiKeys,
authenticators: rootUser.authenticators,
oauthProviders: rootUser.oauthProviders
})),
rootQuorumThreshold: request.rootQuorumThreshold,
wallet: {
walletName: request.wallet.walletName,
accounts: request.wallet.accounts.map((account) => ({
pathFormat: account.pathFormat,
path: account.path,
curve: account.curve,
addressFormat: account.addressFormat
})),
mnemonicLength: request.wallet.mnemonicLength
}
}
};
const stamp = await this.stamper.stamp(JSON.stringify(requestBody));
try {
const response = await axios.post(
`${this.config.baseUrl}/public/v1/submit/create_sub_organization`,
requestBody,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
} catch (error) {
console.log(error.response.data);
throw new Error("Failed to create wallet");
}
}
async addWalletAccount(request) {
try {
console.log(JSON.stringify(request, null, 2));
const stamp = await this.stamper.stamp(JSON.stringify(request));
const response = await axios.post(
`${this.config.baseUrl}/public/v1/submit/create_wallet_accounts`,
request,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
} catch (error) {
console.log(error.response.data);
if (axios.isAxiosError(error)) {
throw new Error(`Axios error: ${error.response?.status} ${error.response?.statusText} - ${JSON.stringify(error.response?.data)}`);
}
throw new Error("Failed to add wallet account");
}
}
async signRawPayload(request) {
const stamp = await this.stamper.stamp(JSON.stringify(request));
const response = await axios.post(
`${this.config.baseUrl}/public/v1/submit/sign_raw_payload`,
request,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
}
async signRawPayloads(request) {
try {
const stamp = await this.stamper.stamp(JSON.stringify(request));
const response = await axios.post(
`${this.config.baseUrl}/public/v1/submit/sign_raw_payloads`,
request,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(`Axios error: ${error.response?.status} ${error.response?.statusText} - ${JSON.stringify(error.response?.data)}`);
}
throw new Error("Failed to sign raw payloads");
}
}
async signTransaction(request) {
const stamp = await this.stamper.stamp(JSON.stringify(request));
const response = await axios.post(
`${this.config.baseUrl}/public/v1/submit/sign_transaction`,
request,
{ headers: { [stamp.stampHeaderName]: stamp.stampHeaderValue } }
);
return response.data;
}
};
export { TurnkeyAuthenticator, TurnkeySDK, getChainIdFromAddressFormat };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map