diff --git a/.changeset/.last_bumped_modules b/.changeset/.last_bumped_modules new file mode 100644 index 0000000..01f5950 --- /dev/null +++ b/.changeset/.last_bumped_modules @@ -0,0 +1 @@ +sdk-types diff --git a/.changeset/change-20260126-10:38:46.yml b/.changeset/change-20260126-10:38:46.yml deleted file mode 100644 index 306df24..0000000 --- a/.changeset/change-20260126-10:38:46.yml +++ /dev/null @@ -1,4 +0,0 @@ -type: minor -packages: [ api-key-stamper, sdk-types, http ] -changelog: |- - Initial release diff --git a/Makefile b/Makefile index 1fefb7a..6e4c46e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: generate generate-types generate-http test format format-check typecheck install clean help changeset changeset-status changeset-version changeset-changelog build +.PHONY: generate generate-types generate-http test format format-check typecheck install clean help changeset changeset-status changeset-version changeset-changelog build prepare-release help: @echo "Available commands:" @@ -17,6 +17,7 @@ help: @echo " make changeset-status - Show pending changesets" @echo " make changeset-version - Apply version bumps from changesets" @echo " make changeset-changelog - Generate changelogs from applied changesets" + @echo " make prepare-release - Run version bump, changelog generation, and build for release preparation" generate: generate-types generate-http @@ -86,3 +87,6 @@ changeset-version: changeset-changelog: @python changesets/manage.py changelog +prepare-release: changeset-version changeset-changelog build + @echo "✅ Release preparation complete" + diff --git a/codegen/constants.py b/codegen/constants.py index 3c326de..ac53c29 100644 --- a/codegen/constants.py +++ b/codegen/constants.py @@ -72,9 +72,19 @@ "v1InitUserEmailRecoveryResult", ), "ACTIVITY_TYPE_INIT_OTP": ( - "ACTIVITY_TYPE_INIT_OTP_V2", - "v1InitOtpIntentV2", - "v1InitOtpResult", + "ACTIVITY_TYPE_INIT_OTP_V3", + "v1InitOtpIntentV3", + "v1InitOtpResultV2", + ), + "ACTIVITY_TYPE_VERIFY_OTP": ( + "ACTIVITY_TYPE_VERIFY_OTP_V2", + "v1VerifyOtpIntentV2", + "v1VerifyOtpResult", + ), + "ACTIVITY_TYPE_OTP_LOGIN": ( + "ACTIVITY_TYPE_OTP_LOGIN_V2", + "v1OtpLoginIntentV2", + "v1OtpLoginResult", ), } diff --git a/codegen/types/generate_types.py b/codegen/types/generate_types.py index db52478..fe70093 100644 --- a/codegen/types/generate_types.py +++ b/codegen/types/generate_types.py @@ -63,6 +63,11 @@ def strip_version_suffix(activity_type: str) -> str: return re.sub(r"(_V\d+)$", "", activity_type) +def escape_description(desc: str) -> str: + """Escape a description string for safe inclusion in generated code.""" + return desc.replace("\\", "\\\\").replace('"', '\\"') + + def is_valid_identifier(name: str) -> bool: """Check if a string is a valid Python identifier.""" return name.isidentifier() @@ -100,7 +105,7 @@ def generate_python_type(name: str, definition: Dict[str, Any]) -> str: field_params.append("default=None") field_params.append(f'alias="{original_prop}"') if desc: - field_params.append(f'description="{desc}"') + field_params.append(f'description="{escape_description(desc)}"') field_def = f"Field({', '.join(field_params)})" output += f" {safe_prop}: {prop_type} = {field_def}\n" elif desc or not is_required: @@ -108,7 +113,7 @@ def generate_python_type(name: str, definition: Dict[str, Any]) -> str: if not is_required: field_params.append("default=None") if desc: - field_params.append(f'description="{desc}"') + field_params.append(f'description="{escape_description(desc)}"') field_def = f"Field({', '.join(field_params)})" output += f" {safe_prop}: {prop_type} = {field_def}\n" else: @@ -163,7 +168,7 @@ def generate_inline_properties( field_params.append("default=None") field_params.append(f'alias="{original_prop}"') if desc: - field_params.append(f'description="{desc}"') + field_params.append(f'description="{escape_description(desc)}"') field_def = f"Field({', '.join(field_params)})" output += f" {safe_prop}: {prop_type} = {field_def}\n" elif desc or not is_required: @@ -171,7 +176,7 @@ def generate_inline_properties( if not is_required: field_params.append("default=None") if desc: - field_params.append(f'description="{desc}"') + field_params.append(f'description="{escape_description(desc)}"') field_def = f"Field({', '.join(field_params)})" output += f" {safe_prop}: {prop_type} = {field_def}\n" else: @@ -382,7 +387,9 @@ def generate_api_types(swagger: Dict[str, Any], prefix: str = "") -> str: field_params.append("default=None") field_params.append(f'alias="{original_prop}"') if desc: - field_params.append(f'description="{desc}"') + field_params.append( + f'description="{escape_description(desc)}"' + ) field_def = f"Field({', '.join(field_params)})" output += f" {safe_prop}: {prop_type} = {field_def}\n" elif desc or not is_required: @@ -390,7 +397,9 @@ def generate_api_types(swagger: Dict[str, Any], prefix: str = "") -> str: if not is_required: field_params.append("default=None") if desc: - field_params.append(f'description="{desc}"') + field_params.append( + f'description="{escape_description(desc)}"' + ) field_def = f"Field({', '.join(field_params)})" output += f" {safe_prop}: {prop_type} = {field_def}\n" else: diff --git a/packages/http/src/turnkey_http/generated/client.py b/packages/http/src/turnkey_http/generated/client.py index 64afdb1..94e9b51 100644 --- a/packages/http/src/turnkey_http/generated/client.py +++ b/packages/http/src/turnkey_http/generated/client.py @@ -396,40 +396,6 @@ def stamp_get_api_keys( url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY ) - def get_attestation_document( - self, input: GetAttestationDocumentBody - ) -> GetAttestationDocumentResponse: - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - - body = {"organizationId": organization_id, **input_dict} - - return self._request( - "/public/v1/query/get_attestation", body, GetAttestationDocumentResponse - ) - - def stamp_get_attestation_document( - self, input: GetAttestationDocumentBody - ) -> SignedRequest: - """Stamp a getAttestationDocument request without sending it.""" - - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - - body = {"organizationId": organization_id, **input_dict} - - full_url = self.base_url + "/public/v1/query/get_attestation" - body_str = self._serialize_body(body) - stamp = self.stamper.stamp(body_str) - - return SignedRequest( - url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY - ) - def get_authenticator( self, input: GetAuthenticatorBody ) -> GetAuthenticatorResponse: @@ -718,46 +684,6 @@ def stamp_get_on_ramp_transaction_status( url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY ) - def get_organization( - self, input: Optional[GetOrganizationBody] = None - ) -> GetOrganizationResponse: - if input is None: - input_dict = {} - else: - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - - body = {"organizationId": organization_id, **input_dict} - - return self._request( - "/public/v1/query/get_organization", body, GetOrganizationResponse - ) - - def stamp_get_organization( - self, input: Optional[GetOrganizationBody] = None - ) -> SignedRequest: - """Stamp a getOrganization request without sending it.""" - - if input is None: - input_dict = {} - else: - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - - body = {"organizationId": organization_id, **input_dict} - - full_url = self.base_url + "/public/v1/query/get_organization" - body_str = self._serialize_body(body) - stamp = self.stamper.stamp(body_str) - - return SignedRequest( - url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY - ) - def get_organization_configs( self, input: GetOrganizationConfigsBody ) -> GetOrganizationConfigsResponse: @@ -1048,6 +974,42 @@ def stamp_get_wallet_account(self, input: GetWalletAccountBody) -> SignedRequest url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY ) + def get_wallet_address_balances( + self, input: GetWalletAddressBalancesBody + ) -> GetWalletAddressBalancesResponse: + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + + body = {"organizationId": organization_id, **input_dict} + + return self._request( + "/public/v1/query/get_wallet_address_balances", + body, + GetWalletAddressBalancesResponse, + ) + + def stamp_get_wallet_address_balances( + self, input: GetWalletAddressBalancesBody + ) -> SignedRequest: + """Stamp a getWalletAddressBalances request without sending it.""" + + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + + body = {"organizationId": organization_id, **input_dict} + + full_url = self.base_url + "/public/v1/query/get_wallet_address_balances" + body_str = self._serialize_body(body) + stamp = self.stamper.stamp(body_str) + + return SignedRequest( + url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY + ) + def get_activities( self, input: Optional[GetActivitiesBody] = None ) -> GetActivitiesResponse: @@ -1380,6 +1342,40 @@ def stamp_get_sub_org_ids( url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY ) + def list_supported_assets( + self, input: ListSupportedAssetsBody + ) -> ListSupportedAssetsResponse: + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + + body = {"organizationId": organization_id, **input_dict} + + return self._request( + "/public/v1/query/list_supported_assets", body, ListSupportedAssetsResponse + ) + + def stamp_list_supported_assets( + self, input: ListSupportedAssetsBody + ) -> SignedRequest: + """Stamp a listSupportedAssets request without sending it.""" + + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + + body = {"organizationId": organization_id, **input_dict} + + full_url = self.base_url + "/public/v1/query/list_supported_assets" + body_str = self._serialize_body(body) + stamp = self.stamper.stamp(body_str) + + return SignedRequest( + url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY + ) + def list_user_tags( self, input: Optional[ListUserTagsBody] = None ) -> ListUserTagsResponse: @@ -1677,55 +1673,6 @@ def stamp_create_api_keys(self, input: CreateApiKeysBody) -> SignedRequest: url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY ) - def create_api_only_users( - self, input: CreateApiOnlyUsersBody - ) -> CreateApiOnlyUsersResponse: - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) - - body = { - "parameters": input_dict, - "organizationId": organization_id, - "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_CREATE_API_ONLY_USERS", - } - - return self._activity( - "/public/v1/submit/create_api_only_users", - body, - "createApiOnlyUsersResult", - CreateApiOnlyUsersResponse, - ) - - def stamp_create_api_only_users( - self, input: CreateApiOnlyUsersBody - ) -> SignedRequest: - """Stamp a createApiOnlyUsers request without sending it.""" - - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) - - body = { - "parameters": input_dict, - "organizationId": organization_id, - "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_CREATE_API_ONLY_USERS", - } - - full_url = self.base_url + "/public/v1/submit/create_api_only_users" - body_str = self._serialize_body(body) - stamp = self.stamper.stamp(body_str) - - return SignedRequest( - url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY - ) - def create_authenticators( self, input: CreateAuthenticatorsBody ) -> CreateAuthenticatorsResponse: @@ -3333,55 +3280,6 @@ def stamp_email_auth(self, input: EmailAuthBody) -> SignedRequest: url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY ) - def eth_send_raw_transaction( - self, input: EthSendRawTransactionBody - ) -> EthSendRawTransactionResponse: - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) - - body = { - "parameters": input_dict, - "organizationId": organization_id, - "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_ETH_SEND_RAW_TRANSACTION", - } - - return self._activity( - "/public/v1/submit/eth_send_raw_transaction", - body, - "ethSendRawTransactionResult", - EthSendRawTransactionResponse, - ) - - def stamp_eth_send_raw_transaction( - self, input: EthSendRawTransactionBody - ) -> SignedRequest: - """Stamp a ethSendRawTransaction request without sending it.""" - - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) - - body = { - "parameters": input_dict, - "organizationId": organization_id, - "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_ETH_SEND_RAW_TRANSACTION", - } - - full_url = self.base_url + "/public/v1/submit/eth_send_raw_transaction" - body_str = self._serialize_body(body) - stamp = self.stamper.stamp(body_str) - - return SignedRequest( - url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY - ) - def eth_send_transaction( self, input: EthSendTransactionBody ) -> EthSendTransactionResponse: @@ -3816,11 +3714,11 @@ def init_otp(self, input: InitOtpBody) -> InitOtpResponse: "parameters": input_dict, "organizationId": organization_id, "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_INIT_OTP_V2", + "type": "ACTIVITY_TYPE_INIT_OTP_V3", } return self._activity( - "/public/v1/submit/init_otp", body, "initOtpResult", InitOtpResponse + "/public/v1/submit/init_otp", body, "initOtpResultV2", InitOtpResponse ) def stamp_init_otp(self, input: InitOtpBody) -> SignedRequest: @@ -3836,7 +3734,7 @@ def stamp_init_otp(self, input: InitOtpBody) -> SignedRequest: "parameters": input_dict, "organizationId": organization_id, "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_INIT_OTP_V2", + "type": "ACTIVITY_TYPE_INIT_OTP_V3", } full_url = self.base_url + "/public/v1/submit/init_otp" @@ -4128,7 +4026,7 @@ def otp_login(self, input: OtpLoginBody) -> OtpLoginResponse: "parameters": input_dict, "organizationId": organization_id, "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_OTP_LOGIN", + "type": "ACTIVITY_TYPE_OTP_LOGIN_V2", } return self._activity( @@ -4148,7 +4046,7 @@ def stamp_otp_login(self, input: OtpLoginBody) -> SignedRequest: "parameters": input_dict, "organizationId": organization_id, "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_OTP_LOGIN", + "type": "ACTIVITY_TYPE_OTP_LOGIN_V2", } full_url = self.base_url + "/public/v1/submit/otp_login" @@ -4479,6 +4377,55 @@ def stamp_sign_transaction(self, input: SignTransactionBody) -> SignedRequest: url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY ) + def sol_send_transaction( + self, input: SolSendTransactionBody + ) -> SolSendTransactionResponse: + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) + + body = { + "parameters": input_dict, + "organizationId": organization_id, + "timestampMs": timestamp_ms, + "type": "ACTIVITY_TYPE_SOL_SEND_TRANSACTION", + } + + return self._activity( + "/public/v1/submit/sol_send_transaction", + body, + "solSendTransactionResult", + SolSendTransactionResponse, + ) + + def stamp_sol_send_transaction( + self, input: SolSendTransactionBody + ) -> SignedRequest: + """Stamp a solSendTransaction request without sending it.""" + + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) + + body = { + "parameters": input_dict, + "organizationId": organization_id, + "timestampMs": timestamp_ms, + "type": "ACTIVITY_TYPE_SOL_SEND_TRANSACTION", + } + + full_url = self.base_url + "/public/v1/submit/sol_send_transaction" + body_str = self._serialize_body(body) + stamp = self.stamper.stamp(body_str) + + return SignedRequest( + url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY + ) + def stamp_login(self, input: StampLoginBody) -> StampLoginResponse: # Convert Pydantic model to dict input_dict = input.model_dump(by_alias=True, exclude_none=True) @@ -4622,6 +4569,55 @@ def stamp_update_oauth2_credential( url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY ) + def update_organization_name( + self, input: UpdateOrganizationNameBody + ) -> UpdateOrganizationNameResponse: + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) + + body = { + "parameters": input_dict, + "organizationId": organization_id, + "timestampMs": timestamp_ms, + "type": "ACTIVITY_TYPE_UPDATE_ORGANIZATION_NAME", + } + + return self._activity( + "/public/v1/submit/update_organization_name", + body, + "updateOrganizationNameResult", + UpdateOrganizationNameResponse, + ) + + def stamp_update_organization_name( + self, input: UpdateOrganizationNameBody + ) -> SignedRequest: + """Stamp a updateOrganizationName request without sending it.""" + + # Convert Pydantic model to dict + input_dict = input.model_dump(by_alias=True, exclude_none=True) + + organization_id = input_dict.pop("organizationId", self.organization_id) + timestamp_ms = input_dict.pop("timestampMs", str(int(time.time() * 1000))) + + body = { + "parameters": input_dict, + "organizationId": organization_id, + "timestampMs": timestamp_ms, + "type": "ACTIVITY_TYPE_UPDATE_ORGANIZATION_NAME", + } + + full_url = self.base_url + "/public/v1/submit/update_organization_name" + body_str = self._serialize_body(body) + stamp = self.stamper.stamp(body_str) + + return SignedRequest( + url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY + ) + def update_policy(self, input: UpdatePolicyBody) -> UpdatePolicyResponse: # Convert Pydantic model to dict input_dict = input.model_dump(by_alias=True, exclude_none=True) @@ -5048,7 +5044,7 @@ def verify_otp(self, input: VerifyOtpBody) -> VerifyOtpResponse: "parameters": input_dict, "organizationId": organization_id, "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_VERIFY_OTP", + "type": "ACTIVITY_TYPE_VERIFY_OTP_V2", } return self._activity( @@ -5068,7 +5064,7 @@ def stamp_verify_otp(self, input: VerifyOtpBody) -> SignedRequest: "parameters": input_dict, "organizationId": organization_id, "timestampMs": timestamp_ms, - "type": "ACTIVITY_TYPE_VERIFY_OTP", + "type": "ACTIVITY_TYPE_VERIFY_OTP_V2", } full_url = self.base_url + "/public/v1/submit/verify_otp" @@ -5078,33 +5074,3 @@ def stamp_verify_otp(self, input: VerifyOtpBody) -> SignedRequest: return SignedRequest( url=full_url, body=body_str, stamp=stamp, type=RequestType.ACTIVITY ) - - def test_rate_limits(self, input: TestRateLimitsBody) -> TestRateLimitsResponse: - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - - body = {"organizationId": organization_id, **input_dict} - - return self._request( - "/tkhq/api/v1/test_rate_limits", body, TestRateLimitsResponse - ) - - def stamp_test_rate_limits(self, input: TestRateLimitsBody) -> SignedRequest: - """Stamp a testRateLimits request without sending it.""" - - # Convert Pydantic model to dict - input_dict = input.model_dump(by_alias=True, exclude_none=True) - - organization_id = input_dict.pop("organizationId", self.organization_id) - - body = {"organizationId": organization_id, **input_dict} - - full_url = self.base_url + "/tkhq/api/v1/test_rate_limits" - body_str = self._serialize_body(body) - stamp = self.stamper.stamp(body_str) - - return SignedRequest( - url=full_url, body=body_str, stamp=stamp, type=RequestType.QUERY - ) diff --git a/packages/http/tests/test_queries.py b/packages/http/tests/test_queries.py index 94a2531..663eb42 100644 --- a/packages/http/tests/test_queries.py +++ b/packages/http/tests/test_queries.py @@ -1,11 +1,5 @@ """Test Turnkey HTTP client query methods""" -from turnkey_sdk_types import ( - GetOrganizationBody, - GetOrganizationResponse, - TurnkeyNetworkError, -) - import pytest @@ -78,44 +72,3 @@ def test_get_wallets(client): print("✅ getWallets successful!") print(f"\nFound {len(response.wallets)} wallets") - - -def test_stamp_get_organization_send_signed_request(client, org_id): - """Stamp a query and submit via send_signed_request.""" - print("\n🔧 Testing stamp + send for getOrganization") - - # Stamp only (queries have flat bodies) - signed_req = client.stamp_get_organization(GetOrganizationBody()) - - # Send stamped request via client with type - org_resp = client.send_signed_request(signed_req, GetOrganizationResponse) - - assert org_resp is not None - assert org_resp.organizationData is not None - assert org_resp.organizationData.organizationId == org_id - print( - f"✅ Stamped getOrganization submitted; org: {org_resp.organizationData.organizationId}" - ) - - -def test_organization_id_override_query(client): - """Test that organizationId in request body overrides client config for queries.""" - print("\n🔧 Testing organizationId override for queries") - - # Create request with WRONG organization ID to prove override works - wrong_org_id = "00000000-0000-0000-0000-000000000000" - request = GetOrganizationBody(organizationId=wrong_org_id) - - # This should fail because we're using a wrong organization ID - with pytest.raises(TurnkeyNetworkError) as exc_info: - client.get_organization(request) - - # Verify the error is related to the wrong organization - error = exc_info.value - error_msg = str(error) - print(f"\n❌ Error message: {error_msg}") - print(f" Status code: {error.status_code}") - print(f"✅ Request failed as expected with wrong organization ID") - - # Assert that we got an error for invalid organization (different error than activities) - assert "invalid organization ID" in error_msg diff --git a/packages/sdk-types/CHANGELOG.md b/packages/sdk-types/CHANGELOG.md new file mode 100644 index 0000000..46484a4 --- /dev/null +++ b/packages/sdk-types/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## 0.1.0 — 2026-03-09 + +### Minor Changes +- Initial release + diff --git a/packages/sdk-types/pyproject.toml b/packages/sdk-types/pyproject.toml index 69b00f8..e716748 100644 --- a/packages/sdk-types/pyproject.toml +++ b/packages/sdk-types/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "turnkey-sdk-types" -version = "0.0.0" +version = "0.1.0" description = "Type definitions for Turnkey API" readme = "README.md" requires-python = ">=3.8" diff --git a/packages/sdk-types/src/turnkey_sdk_types/generated/types.py b/packages/sdk-types/src/turnkey_sdk_types/generated/types.py index 9e66c57..b7360fa 100644 --- a/packages/sdk-types/src/turnkey_sdk_types/generated/types.py +++ b/packages/sdk-types/src/turnkey_sdk_types/generated/types.py @@ -395,6 +395,16 @@ class v1ActivityType(str, Enum): ACTIVITY_TYPE_INIT_OTP_AUTH_V3 = "ACTIVITY_TYPE_INIT_OTP_AUTH_V3" ACTIVITY_TYPE_INIT_OTP_V2 = "ACTIVITY_TYPE_INIT_OTP_V2" ACTIVITY_TYPE_UPSERT_GAS_USAGE_CONFIG = "ACTIVITY_TYPE_UPSERT_GAS_USAGE_CONFIG" + ACTIVITY_TYPE_CREATE_TVC_APP = "ACTIVITY_TYPE_CREATE_TVC_APP" + ACTIVITY_TYPE_CREATE_TVC_DEPLOYMENT = "ACTIVITY_TYPE_CREATE_TVC_DEPLOYMENT" + ACTIVITY_TYPE_CREATE_TVC_MANIFEST_APPROVALS = ( + "ACTIVITY_TYPE_CREATE_TVC_MANIFEST_APPROVALS" + ) + ACTIVITY_TYPE_SOL_SEND_TRANSACTION = "ACTIVITY_TYPE_SOL_SEND_TRANSACTION" + ACTIVITY_TYPE_INIT_OTP_V3 = "ACTIVITY_TYPE_INIT_OTP_V3" + ACTIVITY_TYPE_VERIFY_OTP_V2 = "ACTIVITY_TYPE_VERIFY_OTP_V2" + ACTIVITY_TYPE_OTP_LOGIN_V2 = "ACTIVITY_TYPE_OTP_LOGIN_V2" + ACTIVITY_TYPE_UPDATE_ORGANIZATION_NAME = "ACTIVITY_TYPE_UPDATE_ORGANIZATION_NAME" class v1AddressFormat(str, Enum): @@ -506,6 +516,49 @@ class v1ApproveActivityRequest(TurnkeyBaseModel): generateAppProofs: Optional[bool] = Field(default=None) +class v1AssetBalance(TurnkeyBaseModel): + caip19: Optional[str] = Field( + default=None, description="The caip-19 asset identifier" + ) + symbol: Optional[str] = Field(default=None, description="The asset symbol") + balance: Optional[str] = Field( + default=None, description="The balance in atomic units" + ) + decimals: Optional[int] = Field( + default=None, description="The number of decimals this asset uses" + ) + display: Optional[v1AssetBalanceDisplay] = Field( + default=None, + description="Normalized balance values for display purposes only. Do not do any arithmetic or calculations with these, as the results could be imprecise. Use the balance field instead.", + ) + name: Optional[str] = Field(default=None, description="The asset name") + + +class v1AssetBalanceDisplay(TurnkeyBaseModel): + usd: Optional[str] = Field( + default=None, + description="USD value for display purposes only. Do not do any arithmetic or calculations with these, as the results could be imprecise.", + ) + crypto: Optional[str] = Field( + default=None, + description="Normalized crypto value for display purposes only. Do not do any arithmetic or calculations with these, as the results could be imprecise.", + ) + + +class v1AssetMetadata(TurnkeyBaseModel): + caip19: Optional[str] = Field( + default=None, description="The caip-19 asset identifier" + ) + symbol: Optional[str] = Field(default=None, description="The asset symbol") + decimals: Optional[int] = Field( + default=None, description="The number of decimals this asset uses" + ) + logoUrl: Optional[str] = Field( + default=None, description="The url of the asset logo" + ) + name: Optional[str] = Field(default=None, description="The asset name") + + class v1Attestation(TurnkeyBaseModel): credentialId: str = Field( description="The cbor encoded then base64 url encoded id of the credential." @@ -661,18 +714,6 @@ class v1CreateApiOnlyUsersIntent(TurnkeyBaseModel): ) -class v1CreateApiOnlyUsersRequest(TurnkeyBaseModel): - type: str - timestampMs: str = Field( - description="Timestamp (in milliseconds) of the request, used to verify liveness of user requests." - ) - organizationId: str = Field( - description="Unique identifier for a given Organization." - ) - parameters: v1CreateApiOnlyUsersIntent - generateAppProofs: Optional[bool] = Field(default=None) - - class v1CreateApiOnlyUsersResult(TurnkeyBaseModel): userIds: List[str] = Field(description="A list of API-only User IDs.") @@ -1291,6 +1332,103 @@ class v1CreateSubOrganizationResultV7(TurnkeyBaseModel): rootUserIds: Optional[List[str]] = Field(default=None) +class v1CreateTvcAppIntent(TurnkeyBaseModel): + name: str = Field(description="The name of the new TVC application") + quorumPublicKey: str = Field( + description="Quorum public key to use for this application" + ) + manifestSetId: Optional[str] = Field( + default=None, + description="Unique identifier for an existing TVC operator set to use as the Manifest Set for this TVC application. If left empty, a new Manifest Set configuration is required", + ) + manifestSetParams: Optional[v1TvcOperatorSetParams] = Field( + default=None, + description="Configuration to create a new TVC operator set, used as the Manifest Set for this TVC application. If left empty, a Manifest Set ID is required", + ) + shareSetId: Optional[str] = Field( + default=None, + description="Unique identifier for an existing TVC operator set to use as the Share Set for this TVC application. If left empty, a new Share Set configuration is required", + ) + shareSetParams: Optional[v1TvcOperatorSetParams] = Field( + default=None, + description="Configuration to create a new TVC operator set, used as the Share Set for this TVC application. If left empty, a Share Set ID is required", + ) + enableEgress: Optional[bool] = Field( + default=None, + description="Enables network egress for this TVC app. Default if not provided: false.", + ) + + +class v1CreateTvcAppResult(TurnkeyBaseModel): + appId: str = Field(description="The unique identifier for the TVC application") + manifestSetId: str = Field( + description="The unique identifier for the TVC manifest set" + ) + manifestSetOperatorIds: List[str] = Field( + description="The unique identifier(s) of the manifest set operators" + ) + manifestSetThreshold: int = Field( + description="The required number of approvals for the manifest set" + ) + + +class v1CreateTvcDeploymentIntent(TurnkeyBaseModel): + appId: str = Field( + description="The unique identifier of the to-be-deployed TVC application" + ) + qosVersion: str = Field( + description="The QuorumOS version to use to deploy this application" + ) + pivotContainerImageUrl: str = Field( + description="URL of the container containing the pivot binary" + ) + pivotPath: str = Field(description="Location of the binary in the pivot container") + pivotArgs: List[str] = Field( + description='Arguments to pass to the pivot binary at startup. Encoded as a list of strings, for example ["--foo", "bar"]' + ) + expectedPivotDigest: str = Field( + description="Digest of the pivot binary in the pivot container. This value will be inserted in the QOS manifest to ensure application integrity." + ) + nonce: Optional[int] = Field( + default=None, + description="Optional nonce to ensure uniqueness of the deployment manifest. If not provided, it defaults to the current Unix timestamp in seconds.", + ) + pivotContainerEncryptedPullSecret: Optional[str] = Field( + default=None, + description="Optional encrypted pull secret to authorize Turnkey to pull the pivot container image. If your image is public, leave this empty.", + ) + pivotBindAddresses: Optional[List[str]] = Field( + default=None, + description='Address(es) on which the pivot binary listens. A bind address can be a port alone (e.g. "3000") or an ip:port (e.g. "127.0.0.1:3000"). If provided as a port alone, the IP is assumed to be 0.0.0.0', + ) + debugMode: Optional[bool] = Field( + default=None, + description="Optional flag to indicate whether to deploy the TVC app in debug mode, which includes additional logging and debugging tools. Default is false.", + ) + + +class v1CreateTvcDeploymentResult(TurnkeyBaseModel): + deploymentId: str = Field( + description="The unique identifier for the TVC deployment" + ) + manifestId: str = Field(description="The unique identifier for the TVC manifest") + + +class v1CreateTvcManifestApprovalsIntent(TurnkeyBaseModel): + manifestId: str = Field( + description="Unique identifier of the TVC deployment to approve" + ) + approvals: List[v1TvcManifestApproval] = Field( + description="List of manifest approvals" + ) + + +class v1CreateTvcManifestApprovalsResult(TurnkeyBaseModel): + approvalIds: List[str] = Field( + description="The unique identifier(s) for the manifest approvals" + ) + + class v1CreateUserTagIntent(TurnkeyBaseModel): userTagName: str = Field(description="Human-readable name for a User Tag.") userIds: List[str] = Field(description="A list of User IDs.") @@ -1418,6 +1556,16 @@ class v1CredentialType(str, Enum): class v1Curve(str, Enum): CURVE_SECP256K1 = "CURVE_SECP256K1" CURVE_ED25519 = "CURVE_ED25519" + CURVE_P256 = "CURVE_P256" + + +class v1CustomRevertError(TurnkeyBaseModel): + errorName: Optional[str] = Field( + default=None, description="The name of the custom error." + ) + paramsJson: Optional[str] = Field( + default=None, description="The decoded parameters as a JSON object." + ) class v1DeleteApiKeysIntent(TurnkeyBaseModel): @@ -2033,18 +2181,6 @@ class v1EthSendRawTransactionIntent(TurnkeyBaseModel): ) -class v1EthSendRawTransactionRequest(TurnkeyBaseModel): - type: str - timestampMs: str = Field( - description="Timestamp (in milliseconds) of the request, used to verify liveness of user requests." - ) - organizationId: str = Field( - description="Unique identifier for a given Organization." - ) - parameters: v1EthSendRawTransactionIntent - generateAppProofs: Optional[bool] = Field(default=None) - - class v1EthSendRawTransactionResult(TurnkeyBaseModel): transactionHash: str = Field( description="The transaction hash of the sent transaction" @@ -2105,7 +2241,7 @@ class v1EthSendTransactionRequest(TurnkeyBaseModel): class v1EthSendTransactionResult(TurnkeyBaseModel): sendTransactionStatusId: str = Field( - description="The send_transaction_status ID associated with the transaction submission for sponsored transactions" + description="The send_transaction_status ID associated with the transaction submission" ) @@ -2397,21 +2533,6 @@ class v1GetAppProofsResponse(TurnkeyBaseModel): appProofs: List[v1AppProof] -class v1GetAttestationDocumentRequest(TurnkeyBaseModel): - organizationId: str = Field( - description="Unique identifier for a given organization." - ) - enclaveType: str = Field( - description="The enclave type, one of: ump, notarizer, signer, evm-parser." - ) - - -class v1GetAttestationDocumentResponse(TurnkeyBaseModel): - attestationDocument: str = Field( - description="Raw (CBOR-encoded) attestation document." - ) - - class v1GetAuthenticatorRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given organization." @@ -2476,7 +2597,7 @@ class v1GetNoncesRequest(TurnkeyBaseModel): ) address: str = Field(description="The Ethereum address to query nonces for.") caip2: str = Field( - description="The network identifier in CAIP-2 format (e.g., 'eip155:1' for Ethereum mainnet)." + description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet)." ) nonce: Optional[bool] = Field( default=None, description="Whether to fetch the standard on-chain nonce." @@ -2557,18 +2678,6 @@ class v1GetOrganizationConfigsResponse(TurnkeyBaseModel): ) -class v1GetOrganizationRequest(TurnkeyBaseModel): - organizationId: str = Field( - description="Unique identifier for a given organization." - ) - - -class v1GetOrganizationResponse(TurnkeyBaseModel): - organizationData: v1OrganizationData = Field( - description="Object representing the full current and deleted / disabled collection of users, policies, private keys, and invitations attributable to a particular organization." - ) - - class v1GetPoliciesRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given organization." @@ -2644,6 +2753,10 @@ class v1GetSendTransactionStatusResponse(TurnkeyBaseModel): default=None, description="The error encountered when broadcasting or confirming the transaction, if any.", ) + error: Optional[v1TxError] = Field( + default=None, + description="Structured error information including revert details, if available.", + ) class v1GetSmartContractInterfaceRequest(TurnkeyBaseModel): @@ -2780,6 +2893,22 @@ class v1GetWalletAccountsResponse(TurnkeyBaseModel): ) +class v1GetWalletAddressBalancesRequest(TurnkeyBaseModel): + organizationId: str = Field( + description="Unique identifier for a given organization." + ) + address: str = Field(description="Address corresponding to a wallet account.") + caip2: str = Field( + description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet or 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet). Human-readable Solana aliases ('solana:mainnet', 'solana:devnet') are also accepted and normalized to canonical CAIP-2 values." + ) + + +class v1GetWalletAddressBalancesResponse(TurnkeyBaseModel): + balances: Optional[List[v1AssetBalance]] = Field( + default=None, description="List of asset balances" + ) + + class v1GetWalletRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given organization." @@ -3214,6 +3343,48 @@ class v1InitOtpIntentV2(TurnkeyBaseModel): ) +class v1InitOtpIntentV3(TurnkeyBaseModel): + otpType: str = Field( + description="Whether to send OTP via SMS or email. Possible values: OTP_TYPE_SMS, OTP_TYPE_EMAIL" + ) + contact: str = Field(description="Email or phone number to send the OTP code to") + appName: str = Field(description="The name of the application.") + otpLength: Optional[int] = Field( + default=None, description="Optional length of the OTP code. Default = 9" + ) + emailCustomization: Optional[v1EmailCustomizationParamsV2] = Field( + default=None, + description="Optional parameters for customizing emails. If not provided, the default email will be used.", + ) + smsCustomization: Optional[v1SmsCustomizationParams] = Field( + default=None, + description="Optional parameters for customizing SMS message. If not provided, the default sms message will be used.", + ) + userIdentifier: Optional[str] = Field( + default=None, + description="Optional client-generated user identifier to enable per-user rate limiting for SMS auth. We recommend using a hash of the client-side IP address.", + ) + sendFromEmailAddress: Optional[str] = Field( + default=None, + description="Optional custom email address from which to send the OTP email", + ) + alphanumeric: Optional[bool] = Field( + default=None, + description="Optional flag to specify if the OTP code should be alphanumeric (Crockford’s Base32). If set to false, OTP code will only be numeric. Default = true", + ) + sendFromEmailSenderName: Optional[str] = Field( + default=None, + description="Optional custom sender name for use with sendFromEmailAddress; if left empty, will default to 'Notifications'", + ) + expirationSeconds: Optional[str] = Field( + default=None, + description="Expiration window (in seconds) indicating how long the OTP is valid for. If not provided, a default of 5 minutes will be used. Maximum value is 600 seconds (10 minutes)", + ) + replyToEmailAddress: Optional[str] = Field( + default=None, description="Optional custom email address to use as reply-to" + ) + + class v1InitOtpRequest(TurnkeyBaseModel): type: str timestampMs: str = Field( @@ -3222,7 +3393,7 @@ class v1InitOtpRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given Organization." ) - parameters: v1InitOtpIntentV2 + parameters: v1InitOtpIntentV3 generateAppProofs: Optional[bool] = Field(default=None) @@ -3230,6 +3401,13 @@ class v1InitOtpResult(TurnkeyBaseModel): otpId: str = Field(description="Unique identifier for an OTP authentication") +class v1InitOtpResultV2(TurnkeyBaseModel): + otpId: str = Field(description="Unique identifier for an OTP flow") + otpEncryptionTargetBundle: str = Field( + description="Signed bundle containing a target encryption key to use when submitting OTP codes." + ) + + class v1InitUserEmailRecoveryIntent(TurnkeyBaseModel): email: str = Field(description="Email of the user starting recovery") targetPublicKey: str = Field( @@ -3503,31 +3681,19 @@ class v1Intent(TurnkeyBaseModel): upsertGasUsageConfigIntent: Optional[v1UpsertGasUsageConfigIntent] = Field( default=None ) - - -class v1Invitation(TurnkeyBaseModel): - invitationId: str = Field( - description="Unique identifier for a given Invitation object." - ) - receiverUserName: str = Field( - description="The name of the intended Invitation recipient." - ) - receiverEmail: str = Field( - description="The email address of the intended Invitation recipient." - ) - receiverUserTags: List[str] = Field( - description="A list of tags assigned to the Invitation recipient." - ) - accessType: v1AccessType = Field( - description="The User's permissible access method(s)." + createTvcAppIntent: Optional[v1CreateTvcAppIntent] = Field(default=None) + createTvcDeploymentIntent: Optional[v1CreateTvcDeploymentIntent] = Field( + default=None ) - status: v1InvitationStatus = Field( - description="The current processing status of a specified Invitation." + createTvcManifestApprovalsIntent: Optional[v1CreateTvcManifestApprovalsIntent] = ( + Field(default=None) ) - createdAt: externaldatav1Timestamp - updatedAt: externaldatav1Timestamp - senderUserId: str = Field( - description="Unique identifier for the Sender of an Invitation." + solSendTransactionIntent: Optional[v1SolSendTransactionIntent] = Field(default=None) + initOtpIntentV3: Optional[v1InitOtpIntentV3] = Field(default=None) + verifyOtpIntentV2: Optional[v1VerifyOtpIntentV2] = Field(default=None) + otpLoginIntentV2: Optional[v1OtpLoginIntentV2] = Field(default=None) + updateOrganizationNameIntent: Optional[v1UpdateOrganizationNameIntent] = Field( + default=None ) @@ -3549,12 +3715,6 @@ class v1InvitationParams(TurnkeyBaseModel): ) -class v1InvitationStatus(str, Enum): - INVITATION_STATUS_CREATED = "INVITATION_STATUS_CREATED" - INVITATION_STATUS_ACCEPTED = "INVITATION_STATUS_ACCEPTED" - INVITATION_STATUS_REVOKED = "INVITATION_STATUS_REVOKED" - - class v1ListFiatOnRampCredentialsRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given Organization." @@ -3585,6 +3745,21 @@ class v1ListPrivateKeyTagsResponse(TurnkeyBaseModel): privateKeyTags: List[datav1Tag] = Field(description="A list of private key tags.") +class v1ListSupportedAssetsRequest(TurnkeyBaseModel): + organizationId: str = Field( + description="Unique identifier for a given organization." + ) + caip2: str = Field( + description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet or 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet). Human-readable Solana aliases ('solana:mainnet', 'solana:devnet') are also accepted and normalized to canonical CAIP-2 values." + ) + + +class v1ListSupportedAssetsResponse(TurnkeyBaseModel): + assets: Optional[List[v1AssetMetadata]] = Field( + default=None, description="List of asset metadata" + ) + + class v1ListUserTagsRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given organization." @@ -3616,6 +3791,19 @@ class v1NOOPCodegenAnchorResponse(TurnkeyBaseModel): tokenUsage: Optional[v1TokenUsage] = Field(default=None) +class v1NativeRevertError(TurnkeyBaseModel): + nativeType: Optional[str] = Field( + default=None, + description="The type of native error: 'error_string', 'panic', or 'execution_reverted'.", + ) + message: Optional[str] = Field( + default=None, description="The error message for Error(string) reverts." + ) + panicCode: Optional[str] = Field( + default=None, description="The panic code for Panic(uint256) reverts." + ) + + class v1Oauth2AuthenticateIntent(TurnkeyBaseModel): oauth2CredentialId: str = Field( description="The OAuth 2.0 credential id whose client_id and client_secret will be used in the OAuth 2.0 flow" @@ -3783,22 +3971,6 @@ class v1Operator(str, Enum): OPERATOR_CONTAINS_ALL = "OPERATOR_CONTAINS_ALL" -class v1OrganizationData(TurnkeyBaseModel): - organizationId: Optional[str] = Field(default=None) - name: Optional[str] = Field(default=None) - users: Optional[List[v1User]] = Field(default=None) - policies: Optional[List[v1Policy]] = Field(default=None) - privateKeys: Optional[List[v1PrivateKey]] = Field(default=None) - invitations: Optional[List[v1Invitation]] = Field(default=None) - tags: Optional[List[datav1Tag]] = Field(default=None) - rootQuorum: Optional[externaldatav1Quorum] = Field(default=None) - features: Optional[List[v1Feature]] = Field(default=None) - wallets: Optional[List[v1Wallet]] = Field(default=None) - smartContractInterfaceReferences: Optional[ - List[v1SmartContractInterfaceReference] - ] = Field(default=None) - - class v1OtpAuthIntent(TurnkeyBaseModel): otpId: str = Field( description="ID representing the result of an init OTP activity." @@ -3864,6 +4036,26 @@ class v1OtpLoginIntent(TurnkeyBaseModel): ) +class v1OtpLoginIntentV2(TurnkeyBaseModel): + verificationToken: str = Field( + description="Signed Verification Token containing a unique id, expiry, verification type, contact" + ) + publicKey: str = Field( + description="Client-side public key generated by the user, used as the session public key upon successful login" + ) + clientSignature: v1ClientSignature = Field( + description="Required signature proving authorization for this login. The signature is over the verification token ID and the public key. Required for secure OTP login process." + ) + expirationSeconds: Optional[str] = Field( + default=None, + description="Expiration window (in seconds) indicating how long the Session is valid for. If not provided, a default of 15 minutes will be used.", + ) + invalidateExisting: Optional[bool] = Field( + default=None, + description="Invalidate all other previously generated Login sessions", + ) + + class v1OtpLoginRequest(TurnkeyBaseModel): type: str timestampMs: str = Field( @@ -3872,7 +4064,7 @@ class v1OtpLoginRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given Organization." ) - parameters: v1OtpLoginIntent + parameters: v1OtpLoginIntentV2 generateAppProofs: Optional[bool] = Field(default=None) @@ -4226,6 +4418,40 @@ class v1Result(TurnkeyBaseModel): upsertGasUsageConfigResult: Optional[v1UpsertGasUsageConfigResult] = Field( default=None ) + createTvcAppResult: Optional[v1CreateTvcAppResult] = Field(default=None) + createTvcDeploymentResult: Optional[v1CreateTvcDeploymentResult] = Field( + default=None + ) + createTvcManifestApprovalsResult: Optional[v1CreateTvcManifestApprovalsResult] = ( + Field(default=None) + ) + solSendTransactionResult: Optional[v1SolSendTransactionResult] = Field(default=None) + initOtpResultV2: Optional[v1InitOtpResultV2] = Field(default=None) + updateOrganizationNameResult: Optional[v1UpdateOrganizationNameResult] = Field( + default=None + ) + + +class v1RevertChainEntry(TurnkeyBaseModel): + address: Optional[str] = Field( + default=None, description="The contract address where the revert occurred." + ) + errorType: Optional[str] = Field( + default=None, description="Type of error: 'unknown', 'native', or 'custom'." + ) + displayMessage: Optional[str] = Field( + default=None, description="Human-readable message describing this revert." + ) + unknown: Optional[v1UnknownRevertError] = Field( + default=None, description="Details for unknown error types." + ) + native: Optional[v1NativeRevertError] = Field( + default=None, + description="Details for native Solidity errors (Error, Panic, execution reverted).", + ) + custom: Optional[v1CustomRevertError] = Field( + default=None, description="Details for custom contract errors." + ) class v1RootUserParams(TurnkeyBaseModel): @@ -4453,12 +4679,6 @@ class v1SimpleClientExtensionResults(TurnkeyBaseModel): ) -class v1SmartContractInterfaceReference(TurnkeyBaseModel): - smartContractInterfaceId: Optional[str] = Field(default=None) - smartContractAddress: Optional[str] = Field(default=None) - digest: Optional[str] = Field(default=None) - - class v1SmartContractInterfaceType(str, Enum): SMART_CONTRACT_INTERFACE_TYPE_ETHEREUM = "SMART_CONTRACT_INTERFACE_TYPE_ETHEREUM" SMART_CONTRACT_INTERFACE_TYPE_SOLANA = "SMART_CONTRACT_INTERFACE_TYPE_SOLANA" @@ -4471,6 +4691,43 @@ class v1SmsCustomizationParams(TurnkeyBaseModel): ) +class v1SolSendTransactionIntent(TurnkeyBaseModel): + unsignedTransaction: str = Field( + description="Base64-encoded serialized unsigned Solana transaction" + ) + signWith: str = Field( + description="A wallet or private key address to sign with. This does not support private key IDs." + ) + sponsor: Optional[bool] = Field( + default=None, description="Whether to sponsor this transaction via Gas Station." + ) + caip2: str = Field( + description="CAIP-2 chain ID (e.g., 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet)." + ) + recentBlockhash: Optional[str] = Field( + default=None, + description="user-provided blockhash for replay protection / deadline control. If omitted and sponsor=true, we fetch a fresh blockhash during execution", + ) + + +class v1SolSendTransactionRequest(TurnkeyBaseModel): + type: str + timestampMs: str = Field( + description="Timestamp (in milliseconds) of the request, used to verify liveness of user requests." + ) + organizationId: str = Field( + description="Unique identifier for a given Organization." + ) + parameters: v1SolSendTransactionIntent + generateAppProofs: Optional[bool] = Field(default=None) + + +class v1SolSendTransactionResult(TurnkeyBaseModel): + sendTransactionStatusId: str = Field( + description="The send_transaction_status ID associated with the transaction submission" + ) + + class v1StampLoginIntent(TurnkeyBaseModel): publicKey: str = Field( description="Client-side public key generated by the user, which will be conditionally added to org data based on the passkey stamp associated with this request" @@ -4508,22 +4765,6 @@ class v1TagType(str, Enum): TAG_TYPE_PRIVATE_KEY = "TAG_TYPE_PRIVATE_KEY" -class v1TestRateLimitsRequest(TurnkeyBaseModel): - organizationId: str = Field( - description="Unique identifier for a given organization. If the request is being made by a WebAuthN user and their sub-organization ID is unknown, this can be the parent organization ID; using the sub-organization ID when possible is preferred due to performance reasons." - ) - isSetLimit: bool = Field( - description="Whether or not to set a limit on this request." - ) - limit: int = Field( - description="Rate limit to set for org, if is_set_limit is set to true." - ) - - -class v1TestRateLimitsResponse(TurnkeyBaseModel): - pass - - class v1TokenUsage(TurnkeyBaseModel): type: v1UsageType = Field(description="Type of token usage") tokenId: str = Field(description="Unique identifier for the verification token") @@ -4536,6 +4777,55 @@ class v1TransactionType(str, Enum): TRANSACTION_TYPE_SOLANA = "TRANSACTION_TYPE_SOLANA" TRANSACTION_TYPE_TRON = "TRANSACTION_TYPE_TRON" TRANSACTION_TYPE_BITCOIN = "TRANSACTION_TYPE_BITCOIN" + TRANSACTION_TYPE_TEMPO = "TRANSACTION_TYPE_TEMPO" + + +class v1TvcManifestApproval(TurnkeyBaseModel): + operatorId: str = Field( + description="Unique identifier of the operator providing this approval" + ) + signature: str = Field( + description="Signature from the operator approving the manifest" + ) + + +class v1TvcOperatorParams(TurnkeyBaseModel): + name: str = Field(description="The name for this new operator") + publicKey: str = Field(description="Public key for this operator") + + +class v1TvcOperatorSetParams(TurnkeyBaseModel): + name: str = Field(description="Short description for this new operator set") + newOperators: Optional[List[v1TvcOperatorParams]] = Field( + default=None, description="Operators to create as part of this new operator set" + ) + existingOperatorIds: Optional[List[str]] = Field( + default=None, + description="Existing operators to use as part of this new operator set", + ) + threshold: int = Field( + description="The threshold of operators needed to reach consensus in this new Operator Set" + ) + + +class v1TxError(TurnkeyBaseModel): + message: Optional[str] = Field( + default=None, + description="Human-readable error message describing what went wrong.", + ) + revertChain: Optional[List[v1RevertChainEntry]] = Field( + default=None, + description="Chain of revert errors from nested contract calls, ordered from outermost to innermost.", + ) + + +class v1UnknownRevertError(TurnkeyBaseModel): + selector: Optional[str] = Field( + default=None, description="The 4-byte error selector, if available." + ) + data: Optional[str] = Field( + default=None, description="The raw error data, hex-encoded." + ) class v1UpdateAllowedOriginsIntent(TurnkeyBaseModel): @@ -4600,6 +4890,10 @@ class v1UpdateAuthProxyConfigIntent(TurnkeyBaseModel): default=None, description="Verification token required for get account with PII (email/phone number). Default false.", ) + socialLinkingClientIds: Optional[List[str]] = Field( + default=None, + description="Whitelisted OAuth client IDs for social account linking. When a user authenticates via a social provider with an email matching an existing account, the accounts will be linked if the client ID is in this list and the issuer is considered a trusted provider.", + ) class v1UpdateAuthProxyConfigResult(TurnkeyBaseModel): @@ -4679,6 +4973,26 @@ class v1UpdateOauth2CredentialResult(TurnkeyBaseModel): ) +class v1UpdateOrganizationNameIntent(TurnkeyBaseModel): + organizationName: str = Field(description="New name for the Organization.") + + +class v1UpdateOrganizationNameRequest(TurnkeyBaseModel): + type: str + timestampMs: str = Field( + description="Timestamp (in milliseconds) of the request, used to verify liveness of user requests." + ) + organizationId: str = Field( + description="Unique identifier for a given Organization." + ) + parameters: v1UpdateOrganizationNameIntent + + +class v1UpdateOrganizationNameResult(TurnkeyBaseModel): + organizationId: str = Field(description="Unique identifier for the Organization.") + organizationName: str = Field(description="The updated organization name.") + + class v1UpdatePolicyIntent(TurnkeyBaseModel): policyId: str = Field(description="Unique identifier for a given Policy.") policyName: Optional[str] = Field( @@ -4978,6 +5292,10 @@ class v1UpsertGasUsageConfigIntent(TurnkeyBaseModel): windowDurationMinutes: str = Field( description="Rolling sponsorship window duration, expressed in minutes." ) + enabled: Optional[bool] = Field( + default=None, + description="Whether gas sponsorship is enabled for the organization.", + ) class v1UpsertGasUsageConfigResult(TurnkeyBaseModel): @@ -5088,6 +5406,19 @@ class v1VerifyOtpIntent(TurnkeyBaseModel): ) +class v1VerifyOtpIntentV2(TurnkeyBaseModel): + otpId: str = Field( + description="UUID representing an OTP flow. A new UUID is created for each init OTP activity." + ) + encryptedOtpBundle: str = Field( + description="Encrypted bundle containing the OTP code and a client-generated public key. Turnkey's secure enclaves will decrypt this bundle, verify the OTP code, and issue a new Verification Token. Encrypted using the target encryption key provided in the INIT_OTP activity result." + ) + expirationSeconds: Optional[str] = Field( + default=None, + description="Expiration window (in seconds) indicating how long the verification token is valid for. If not provided, a default of 1 hour will be used. Maximum value is 86400 seconds (24 hours)", + ) + + class v1VerifyOtpRequest(TurnkeyBaseModel): type: str timestampMs: str = Field( @@ -5096,7 +5427,7 @@ class v1VerifyOtpRequest(TurnkeyBaseModel): organizationId: str = Field( description="Unique identifier for a given Organization." ) - parameters: v1VerifyOtpIntent + parameters: v1VerifyOtpIntentV2 generateAppProofs: Optional[bool] = Field(default=None) @@ -5274,23 +5605,6 @@ class GetApiKeysInput(TurnkeyBaseModel): body: GetApiKeysBody -class GetAttestationDocumentResponse(TurnkeyBaseModel): - attestationDocument: str = Field( - description="Raw (CBOR-encoded) attestation document." - ) - - -class GetAttestationDocumentBody(TurnkeyBaseModel): - organizationId: Optional[str] = None - enclaveType: str = Field( - description="The enclave type, one of: ump, notarizer, signer, evm-parser." - ) - - -class GetAttestationDocumentInput(TurnkeyBaseModel): - body: GetAttestationDocumentBody - - class GetAuthenticatorResponse(TurnkeyBaseModel): authenticator: v1Authenticator = Field(description="An authenticator.") @@ -5382,7 +5696,7 @@ class GetNoncesBody(TurnkeyBaseModel): organizationId: Optional[str] = None address: str = Field(description="The Ethereum address to query nonces for.") caip2: str = Field( - description="The network identifier in CAIP-2 format (e.g., 'eip155:1' for Ethereum mainnet)." + description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet)." ) nonce: Optional[bool] = Field( default=None, description="Whether to fetch the standard on-chain nonce." @@ -5450,20 +5764,6 @@ class GetOnRampTransactionStatusInput(TurnkeyBaseModel): body: GetOnRampTransactionStatusBody -class GetOrganizationResponse(TurnkeyBaseModel): - organizationData: v1OrganizationData = Field( - description="Object representing the full current and deleted / disabled collection of users, policies, private keys, and invitations attributable to a particular organization." - ) - - -class GetOrganizationBody(TurnkeyBaseModel): - organizationId: Optional[str] = None - - -class GetOrganizationInput(TurnkeyBaseModel): - body: GetOrganizationBody - - class GetOrganizationConfigsResponse(TurnkeyBaseModel): configs: v1Config = Field( description="Organization configs including quorum settings and organization features." @@ -5530,6 +5830,10 @@ class GetSendTransactionStatusResponse(TurnkeyBaseModel): default=None, description="The error encountered when broadcasting or confirming the transaction, if any.", ) + error: Optional[v1TxError] = Field( + default=None, + description="Structured error information including revert details, if available.", + ) class GetSendTransactionStatusBody(TurnkeyBaseModel): @@ -5607,6 +5911,24 @@ class GetWalletAccountInput(TurnkeyBaseModel): body: GetWalletAccountBody +class GetWalletAddressBalancesResponse(TurnkeyBaseModel): + balances: Optional[List[v1AssetBalance]] = Field( + default=None, description="List of asset balances" + ) + + +class GetWalletAddressBalancesBody(TurnkeyBaseModel): + organizationId: Optional[str] = None + address: str = Field(description="Address corresponding to a wallet account.") + caip2: str = Field( + description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet or 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet). Human-readable Solana aliases ('solana:mainnet', 'solana:devnet') are also accepted and normalized to canonical CAIP-2 values." + ) + + +class GetWalletAddressBalancesInput(TurnkeyBaseModel): + body: GetWalletAddressBalancesBody + + class GetActivitiesResponse(TurnkeyBaseModel): activities: List[v1Activity] = Field(description="A list of activities.") @@ -5742,6 +6064,23 @@ class GetSubOrgIdsInput(TurnkeyBaseModel): body: GetSubOrgIdsBody +class ListSupportedAssetsResponse(TurnkeyBaseModel): + assets: Optional[List[v1AssetMetadata]] = Field( + default=None, description="List of asset metadata" + ) + + +class ListSupportedAssetsBody(TurnkeyBaseModel): + organizationId: Optional[str] = None + caip2: str = Field( + description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet or 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet). Human-readable Solana aliases ('solana:mainnet', 'solana:devnet') are also accepted and normalized to canonical CAIP-2 values." + ) + + +class ListSupportedAssetsInput(TurnkeyBaseModel): + body: ListSupportedAssetsBody + + class ListUserTagsResponse(TurnkeyBaseModel): userTags: List[datav1Tag] = Field(description="A list of user tags.") @@ -5879,23 +6218,6 @@ class CreateApiKeysInput(TurnkeyBaseModel): body: CreateApiKeysBody -class CreateApiOnlyUsersResponse(TurnkeyBaseModel): - activity: v1Activity - userIds: List[str] = Field(description="A list of API-only User IDs.") - - -class CreateApiOnlyUsersBody(TurnkeyBaseModel): - timestampMs: Optional[str] = None - organizationId: Optional[str] = None - apiOnlyUsers: List[v1ApiOnlyUserParams] = Field( - description="A list of API-only Users to create." - ) - - -class CreateApiOnlyUsersInput(TurnkeyBaseModel): - body: CreateApiOnlyUsersBody - - class CreateAuthenticatorsResponse(TurnkeyBaseModel): activity: v1Activity authenticatorIds: List[str] = Field(description="A list of Authenticator IDs.") @@ -6651,32 +6973,10 @@ class EmailAuthInput(TurnkeyBaseModel): body: EmailAuthBody -class EthSendRawTransactionResponse(TurnkeyBaseModel): - activity: v1Activity - transactionHash: str = Field( - description="The transaction hash of the sent transaction" - ) - - -class EthSendRawTransactionBody(TurnkeyBaseModel): - timestampMs: Optional[str] = None - organizationId: Optional[str] = None - signedTransaction: str = Field( - description="The raw, signed transaction to be sent." - ) - caip2: str = Field( - description="CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet)." - ) - - -class EthSendRawTransactionInput(TurnkeyBaseModel): - body: EthSendRawTransactionBody - - class EthSendTransactionResponse(TurnkeyBaseModel): activity: v1Activity sendTransactionStatusId: str = Field( - description="The send_transaction_status ID associated with the transaction submission for sponsored transactions" + description="The send_transaction_status ID associated with the transaction submission" ) @@ -6941,7 +7241,10 @@ class InitImportWalletInput(TurnkeyBaseModel): class InitOtpResponse(TurnkeyBaseModel): activity: v1Activity - otpId: str = Field(description="Unique identifier for an OTP authentication") + otpId: str = Field(description="Unique identifier for an OTP flow") + otpEncryptionTargetBundle: str = Field( + description="Signed bundle containing a target encryption key to use when submitting OTP codes." + ) class InitOtpBody(TurnkeyBaseModel): @@ -6951,19 +7254,17 @@ class InitOtpBody(TurnkeyBaseModel): description="Whether to send OTP via SMS or email. Possible values: OTP_TYPE_SMS, OTP_TYPE_EMAIL" ) contact: str = Field(description="Email or phone number to send the OTP code to") + appName: str = Field(description="The name of the application.") otpLength: Optional[int] = Field( default=None, description="Optional length of the OTP code. Default = 9" ) - appName: str = Field( - description="The name of the application. This field is required and will be used in email notifications if an email template is not provided." - ) emailCustomization: Optional[v1EmailCustomizationParamsV2] = Field( default=None, description="Optional parameters for customizing emails. If not provided, the default email will be used.", ) smsCustomization: Optional[v1SmsCustomizationParams] = Field( default=None, - description="Optional parameters for customizing SMS message. If not provided, the default SMS message will be used.", + description="Optional parameters for customizing SMS message. If not provided, the default sms message will be used.", ) userIdentifier: Optional[str] = Field( default=None, @@ -6975,7 +7276,7 @@ class InitOtpBody(TurnkeyBaseModel): ) alphanumeric: Optional[bool] = Field( default=None, - description="Optional flag to specify if the OTP code should be alphanumeric (Crockford’s Base32). Default = true", + description="Optional flag to specify if the OTP code should be alphanumeric (Crockford’s Base32). If set to false, OTP code will only be numeric. Default = true", ) sendFromEmailSenderName: Optional[str] = Field( default=None, @@ -7231,10 +7532,13 @@ class OtpLoginBody(TurnkeyBaseModel): timestampMs: Optional[str] = None organizationId: Optional[str] = None verificationToken: str = Field( - description="Signed JWT containing a unique id, expiry, verification type, contact" + description="Signed Verification Token containing a unique id, expiry, verification type, contact" ) publicKey: str = Field( - description="Client-side public key generated by the user, which will be conditionally added to org data based on the validity of the verification token" + description="Client-side public key generated by the user, used as the session public key upon successful login" + ) + clientSignature: v1ClientSignature = Field( + description="Required signature proving authorization for this login. The signature is over the verification token ID and the public key. Required for secure OTP login process." ) expirationSeconds: Optional[str] = Field( default=None, @@ -7242,11 +7546,7 @@ class OtpLoginBody(TurnkeyBaseModel): ) invalidateExisting: Optional[bool] = Field( default=None, - description="Invalidate all other previously generated Login API keys", - ) - clientSignature: Optional[v1ClientSignature] = Field( - default=None, - description="Optional signature proving authorization for this login. The signature is over the verification token ID and the public key. Only required if a public key was provided during the verification step.", + description="Invalidate all other previously generated Login sessions", ) @@ -7400,6 +7700,38 @@ class SignTransactionInput(TurnkeyBaseModel): body: SignTransactionBody +class SolSendTransactionResponse(TurnkeyBaseModel): + activity: v1Activity + sendTransactionStatusId: str = Field( + description="The send_transaction_status ID associated with the transaction submission" + ) + + +class SolSendTransactionBody(TurnkeyBaseModel): + timestampMs: Optional[str] = None + organizationId: Optional[str] = None + unsignedTransaction: str = Field( + description="Base64-encoded serialized unsigned Solana transaction" + ) + signWith: str = Field( + description="A wallet or private key address to sign with. This does not support private key IDs." + ) + sponsor: Optional[bool] = Field( + default=None, description="Whether to sponsor this transaction via Gas Station." + ) + caip2: str = Field( + description="CAIP-2 chain ID (e.g., 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet)." + ) + recentBlockhash: Optional[str] = Field( + default=None, + description="user-provided blockhash for replay protection / deadline control. If omitted and sponsor=true, we fetch a fresh blockhash during execution", + ) + + +class SolSendTransactionInput(TurnkeyBaseModel): + body: SolSendTransactionBody + + class StampLoginResponse(TurnkeyBaseModel): activity: v1Activity session: str = Field( @@ -7487,6 +7819,22 @@ class UpdateOauth2CredentialInput(TurnkeyBaseModel): body: UpdateOauth2CredentialBody +class UpdateOrganizationNameResponse(TurnkeyBaseModel): + activity: v1Activity + organizationId: str = Field(description="Unique identifier for the Organization.") + organizationName: str = Field(description="The updated organization name.") + + +class UpdateOrganizationNameBody(TurnkeyBaseModel): + timestampMs: Optional[str] = None + organizationId: Optional[str] = None + organizationName: str = Field(description="New name for the Organization.") + + +class UpdateOrganizationNameInput(TurnkeyBaseModel): + body: UpdateOrganizationNameBody + + class UpdatePolicyResponse(TurnkeyBaseModel): activity: v1Activity policyId: str = Field(description="Unique identifier for a given Policy.") @@ -7715,17 +8063,15 @@ class VerifyOtpBody(TurnkeyBaseModel): timestampMs: Optional[str] = None organizationId: Optional[str] = None otpId: str = Field( - description="ID representing the result of an init OTP activity." + description="UUID representing an OTP flow. A new UUID is created for each init OTP activity." + ) + encryptedOtpBundle: str = Field( + description="Encrypted bundle containing the OTP code and a client-generated public key. Turnkey's secure enclaves will decrypt this bundle, verify the OTP code, and issue a new Verification Token. Encrypted using the target encryption key provided in the INIT_OTP activity result." ) - otpCode: str = Field(description="OTP sent out to a user's contact (email or SMS)") expirationSeconds: Optional[str] = Field( default=None, description="Expiration window (in seconds) indicating how long the verification token is valid for. If not provided, a default of 1 hour will be used. Maximum value is 86400 seconds (24 hours)", ) - publicKey: Optional[str] = Field( - default=None, - description="Client-side public key generated by the user, which will be added to the JWT response and verified in subsequent requests via a client proof signature", - ) class VerifyOtpInput(TurnkeyBaseModel): @@ -7735,21 +8081,3 @@ class VerifyOtpInput(TurnkeyBaseModel): class NOOPCodegenAnchorResponse(TurnkeyBaseModel): stamp: v1WebAuthnStamp tokenUsage: Optional[v1TokenUsage] = Field(default=None) - - -class TestRateLimitsResponse(TurnkeyBaseModel): - pass - - -class TestRateLimitsBody(TurnkeyBaseModel): - organizationId: Optional[str] = None - isSetLimit: bool = Field( - description="Whether or not to set a limit on this request." - ) - limit: int = Field( - description="Rate limit to set for org, if is_set_limit is set to true." - ) - - -class TestRateLimitsInput(TurnkeyBaseModel): - body: TestRateLimitsBody diff --git a/schema/public_api.swagger.json b/schema/public_api.swagger.json index eb60427..f659965 100644 --- a/schema/public_api.swagger.json +++ b/schema/public_api.swagger.json @@ -40,7 +40,7 @@ }, { "name": "Users", - "description": "Users are responsible for any action taken within an Organization. They can have ApiKey or Auuthenticator credentials, allowing you to onboard teammates to the Organization, or create API-only Users to run as part of your infrastructure." + "description": "Users are responsible for any action taken within an Organization. They can have ApiKey or Authenticator credentials, allowing you to onboard teammates to the Organization, or create API-only Users to run as part of your infrastructure." }, { "name": "User Tags", @@ -60,7 +60,7 @@ }, { "name": "Consensus", - "description": "Policies can enforce consensus requirements for Activities. For example, adding a new user requires two admins to approve the request.\n\nActivities that have been proposed, but don't yet meet the Consesnsus requirements will have the status: `REQUIRES_CONSENSUS`. Activities in this state can be approved or rejected using the unique fingerprint generated when an Activity is created." + "description": "Policies can enforce consensus requirements for Activities. For example, adding a new user requires two admins to approve the request.\n\nActivities that have been proposed, but don't yet meet the Consensus requirements will have the status: `REQUIRES_CONSENSUS`. Activities in this state can be approved or rejected using the unique fingerprint generated when an Activity is created." } ], "host": "api.turnkey.com", @@ -164,38 +164,6 @@ "tags": ["API keys"] } }, - "/public/v1/query/get_attestation": { - "post": { - "summary": "Attestation", - "description": "Get the attestation document corresponding to an enclave.", - "operationId": "PublicApiService_GetAttestationDocument", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1GetAttestationDocumentResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1GetAttestationDocumentRequest" - } - } - ], - "tags": ["Attestation"] - } - }, "/public/v1/query/get_authenticator": { "post": { "summary": "Get authenticator", @@ -294,7 +262,7 @@ }, "/public/v1/query/get_gas_usage": { "post": { - "summary": "Get gas usage and limits.", + "summary": "Get gas usage", "description": "Get gas usage and gas limits for either the parent organization or a sub-organization.", "operationId": "PublicApiService_GetGasUsage", "responses": { @@ -358,7 +326,7 @@ }, "/public/v1/query/get_nonces": { "post": { - "summary": "Get nonces for an address.", + "summary": "Get nonces", "description": "Get nonce values for an address on a given network. Can fetch the standard on-chain nonce and/or the gas station nonce used for sponsored transactions.", "operationId": "PublicApiService_GetNonces", "responses": { @@ -484,38 +452,6 @@ "tags": ["On Ramp"] } }, - "/public/v1/query/get_organization": { - "post": { - "summary": "Get organization", - "description": "Get details about an organization.", - "operationId": "PublicApiService_GetOrganization", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1GetOrganizationResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1GetOrganizationRequest" - } - } - ], - "tags": ["Organizations"] - } - }, "/public/v1/query/get_organization_configs": { "post": { "summary": "Get configs", @@ -804,6 +740,38 @@ "tags": ["Wallets"] } }, + "/public/v1/query/get_wallet_address_balances": { + "post": { + "summary": "Get balances", + "description": "Get balances of supported assets for an address on the specified network. Only non-zero balances are returned. This feature is in beta - please contact support for access.", + "operationId": "PublicApiService_GetWalletAddressBalances", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetWalletAddressBalancesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1GetWalletAddressBalancesRequest" + } + } + ], + "tags": ["Wallets"] + } + }, "/public/v1/query/list_activities": { "post": { "summary": "List activities", @@ -1092,6 +1060,38 @@ "tags": ["Organizations"] } }, + "/public/v1/query/list_supported_assets": { + "post": { + "summary": "List supported assets", + "description": "List supported assets for the specified network. This feature is in beta - please contact support for access.", + "operationId": "PublicApiService_ListSupportedAssets", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListSupportedAssetsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ListSupportedAssetsRequest" + } + } + ], + "tags": ["Wallets"] + } + }, "/public/v1/query/list_user_tags": { "post": { "summary": "List user tags", @@ -1348,38 +1348,6 @@ "tags": ["API Keys"] } }, - "/public/v1/submit/create_api_only_users": { - "post": { - "summary": "Create API-only users", - "description": "Create API-only users in an existing organization.", - "operationId": "PublicApiService_CreateApiOnlyUsers", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1ActivityResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1CreateApiOnlyUsersRequest" - } - } - ], - "tags": ["Users"] - } - }, "/public/v1/submit/create_authenticators": { "post": { "summary": "Create authenticators", @@ -2468,42 +2436,10 @@ "tags": ["User Auth"] } }, - "/public/v1/submit/eth_send_raw_transaction": { - "post": { - "summary": "Submit a raw transaction for broadcasting.", - "description": "Submit a raw transaction (serialized and signed) for broadcasting to the network.", - "operationId": "PublicApiService_EthSendRawTransaction", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1ActivityResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1EthSendRawTransactionRequest" - } - } - ], - "tags": ["Broadcasting"] - } - }, "/public/v1/submit/eth_send_transaction": { "post": { - "summary": "Submit a transaction intent for broadcasting.", - "description": "Submit a transaction intent describing a transaction you would like to broadcast.", + "summary": "Broadcast EVM transaction", + "description": "Submit a transaction intent describing an EVM transaction you would like to broadcast.", "operationId": "PublicApiService_EthSendTransaction", "responses": { "200": { @@ -3268,6 +3204,38 @@ "tags": ["Signing"] } }, + "/public/v1/submit/sol_send_transaction": { + "post": { + "summary": "Broadcast SVM transaction", + "description": "Submit a transaction intent describing an SVM transaction you would like to broadcast.", + "operationId": "PublicApiService_SolSendTransaction", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ActivityResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1SolSendTransactionRequest" + } + } + ], + "tags": ["Broadcasting"] + } + }, "/public/v1/submit/stamp_login": { "post": { "summary": "Login with a stamp", @@ -3364,6 +3332,38 @@ "tags": ["User Auth"] } }, + "/public/v1/submit/update_organization_name": { + "post": { + "summary": "Update organization name", + "description": "Update the name of an organization.", + "operationId": "PublicApiService_UpdateOrganizationName", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ActivityResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1UpdateOrganizationNameRequest" + } + } + ], + "tags": ["Organizations"] + } + }, "/public/v1/submit/update_policy": { "post": { "summary": "Update policy", @@ -3703,38 +3703,6 @@ }, "tags": ["PublicApiService"] } - }, - "/tkhq/api/v1/test_rate_limits": { - "post": { - "summary": "Test rate limit", - "description": "Set a rate local rate limit just on the current endpoint, for purposes of testing with Vivosuite.", - "operationId": "PublicApiService_TestRateLimits", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1TestRateLimitsResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1TestRateLimitsRequest" - } - } - ], - "tags": ["RateLimit"] - } } }, "definitions": { @@ -4359,7 +4327,15 @@ "ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY_V2", "ACTIVITY_TYPE_INIT_OTP_AUTH_V3", "ACTIVITY_TYPE_INIT_OTP_V2", - "ACTIVITY_TYPE_UPSERT_GAS_USAGE_CONFIG" + "ACTIVITY_TYPE_UPSERT_GAS_USAGE_CONFIG", + "ACTIVITY_TYPE_CREATE_TVC_APP", + "ACTIVITY_TYPE_CREATE_TVC_DEPLOYMENT", + "ACTIVITY_TYPE_CREATE_TVC_MANIFEST_APPROVALS", + "ACTIVITY_TYPE_SOL_SEND_TRANSACTION", + "ACTIVITY_TYPE_INIT_OTP_V3", + "ACTIVITY_TYPE_VERIFY_OTP_V2", + "ACTIVITY_TYPE_OTP_LOGIN_V2", + "ACTIVITY_TYPE_UPDATE_ORGANIZATION_NAME" ] }, "v1AddressFormat": { @@ -4553,29 +4529,98 @@ }, "required": ["type", "timestampMs", "organizationId", "parameters"] }, - "v1Attestation": { + "v1AssetBalance": { "type": "object", "properties": { - "credentialId": { + "caip19": { "type": "string", - "description": "The cbor encoded then base64 url encoded id of the credential." + "description": "The caip-19 asset identifier" }, - "clientDataJson": { + "symbol": { "type": "string", - "description": "A base64 url encoded payload containing metadata about the signing context and the challenge." + "description": "The asset symbol" }, - "attestationObject": { + "balance": { "type": "string", - "description": "A base64 url encoded payload containing authenticator data and any attestation the webauthn provider chooses." + "description": "The balance in atomic units" }, - "transports": { - "type": "array", - "items": { - "$ref": "#/definitions/v1AuthenticatorTransport" - }, - "description": "The type of authenticator transports." + "decimals": { + "type": "integer", + "format": "int32", + "description": "The number of decimals this asset uses" + }, + "display": { + "$ref": "#/definitions/v1AssetBalanceDisplay", + "description": "Normalized balance values for display purposes only. Do not do any arithmetic or calculations with these, as the results could be imprecise. Use the balance field instead." + }, + "name": { + "type": "string", + "description": "The asset name" } - }, + } + }, + "v1AssetBalanceDisplay": { + "type": "object", + "properties": { + "usd": { + "type": "string", + "description": "USD value for display purposes only. Do not do any arithmetic or calculations with these, as the results could be imprecise." + }, + "crypto": { + "type": "string", + "description": "Normalized crypto value for display purposes only. Do not do any arithmetic or calculations with these, as the results could be imprecise." + } + } + }, + "v1AssetMetadata": { + "type": "object", + "properties": { + "caip19": { + "type": "string", + "description": "The caip-19 asset identifier" + }, + "symbol": { + "type": "string", + "description": "The asset symbol" + }, + "decimals": { + "type": "integer", + "format": "int32", + "description": "The number of decimals this asset uses" + }, + "logoUrl": { + "type": "string", + "description": "The url of the asset logo" + }, + "name": { + "type": "string", + "description": "The asset name" + } + } + }, + "v1Attestation": { + "type": "object", + "properties": { + "credentialId": { + "type": "string", + "description": "The cbor encoded then base64 url encoded id of the credential." + }, + "clientDataJson": { + "type": "string", + "description": "A base64 url encoded payload containing metadata about the signing context and the challenge." + }, + "attestationObject": { + "type": "string", + "description": "A base64 url encoded payload containing authenticator data and any attestation the webauthn provider chooses." + }, + "transports": { + "type": "array", + "items": { + "$ref": "#/definitions/v1AuthenticatorTransport" + }, + "description": "The type of authenticator transports." + } + }, "required": [ "credentialId", "clientDataJson", @@ -4895,30 +4940,6 @@ }, "required": ["apiOnlyUsers"] }, - "v1CreateApiOnlyUsersRequest": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["ACTIVITY_TYPE_CREATE_API_ONLY_USERS"] - }, - "timestampMs": { - "type": "string", - "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests." - }, - "organizationId": { - "type": "string", - "description": "Unique identifier for a given Organization." - }, - "parameters": { - "$ref": "#/definitions/v1CreateApiOnlyUsersIntent" - }, - "generateAppProofs": { - "type": "boolean" - } - }, - "required": ["type", "timestampMs", "organizationId", "parameters"] - }, "v1CreateApiOnlyUsersResult": { "type": "object", "properties": { @@ -6221,6 +6242,176 @@ }, "required": ["subOrganizationId"] }, + "v1CreateTvcAppIntent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the new TVC application" + }, + "quorumPublicKey": { + "type": "string", + "description": "Quorum public key to use for this application" + }, + "manifestSetId": { + "type": "string", + "description": "Unique identifier for an existing TVC operator set to use as the Manifest Set for this TVC application. If left empty, a new Manifest Set configuration is required" + }, + "manifestSetParams": { + "$ref": "#/definitions/v1TvcOperatorSetParams", + "description": "Configuration to create a new TVC operator set, used as the Manifest Set for this TVC application. If left empty, a Manifest Set ID is required" + }, + "shareSetId": { + "type": "string", + "description": "Unique identifier for an existing TVC operator set to use as the Share Set for this TVC application. If left empty, a new Share Set configuration is required" + }, + "shareSetParams": { + "$ref": "#/definitions/v1TvcOperatorSetParams", + "description": "Configuration to create a new TVC operator set, used as the Share Set for this TVC application. If left empty, a Share Set ID is required" + }, + "enableEgress": { + "type": "boolean", + "description": "Enables network egress for this TVC app. Default if not provided: false." + } + }, + "required": ["name", "quorumPublicKey"] + }, + "v1CreateTvcAppResult": { + "type": "object", + "properties": { + "appId": { + "type": "string", + "description": "The unique identifier for the TVC application" + }, + "manifestSetId": { + "type": "string", + "description": "The unique identifier for the TVC manifest set" + }, + "manifestSetOperatorIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The unique identifier(s) of the manifest set operators" + }, + "manifestSetThreshold": { + "type": "integer", + "format": "int64", + "description": "The required number of approvals for the manifest set" + } + }, + "required": [ + "appId", + "manifestSetId", + "manifestSetOperatorIds", + "manifestSetThreshold" + ] + }, + "v1CreateTvcDeploymentIntent": { + "type": "object", + "properties": { + "appId": { + "type": "string", + "description": "The unique identifier of the to-be-deployed TVC application" + }, + "qosVersion": { + "type": "string", + "description": "The QuorumOS version to use to deploy this application" + }, + "pivotContainerImageUrl": { + "type": "string", + "description": "URL of the container containing the pivot binary" + }, + "pivotPath": { + "type": "string", + "description": "Location of the binary in the pivot container" + }, + "pivotArgs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Arguments to pass to the pivot binary at startup. Encoded as a list of strings, for example [\"--foo\", \"bar\"]" + }, + "expectedPivotDigest": { + "type": "string", + "description": "Digest of the pivot binary in the pivot container. This value will be inserted in the QOS manifest to ensure application integrity." + }, + "nonce": { + "type": "integer", + "format": "int64", + "description": "Optional nonce to ensure uniqueness of the deployment manifest. If not provided, it defaults to the current Unix timestamp in seconds." + }, + "pivotContainerEncryptedPullSecret": { + "type": "string", + "description": "Optional encrypted pull secret to authorize Turnkey to pull the pivot container image. If your image is public, leave this empty." + }, + "pivotBindAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Address(es) on which the pivot binary listens. A bind address can be a port alone (e.g. \"3000\") or an ip:port (e.g. \"127.0.0.1:3000\"). If provided as a port alone, the IP is assumed to be 0.0.0.0" + }, + "debugMode": { + "type": "boolean", + "description": "Optional flag to indicate whether to deploy the TVC app in debug mode, which includes additional logging and debugging tools. Default is false." + } + }, + "required": [ + "appId", + "qosVersion", + "pivotContainerImageUrl", + "pivotPath", + "pivotArgs", + "expectedPivotDigest" + ] + }, + "v1CreateTvcDeploymentResult": { + "type": "object", + "properties": { + "deploymentId": { + "type": "string", + "description": "The unique identifier for the TVC deployment" + }, + "manifestId": { + "type": "string", + "description": "The unique identifier for the TVC manifest" + } + }, + "required": ["deploymentId", "manifestId"] + }, + "v1CreateTvcManifestApprovalsIntent": { + "type": "object", + "properties": { + "manifestId": { + "type": "string", + "description": "Unique identifier of the TVC deployment to approve" + }, + "approvals": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1TvcManifestApproval" + }, + "description": "List of manifest approvals" + } + }, + "required": ["manifestId", "approvals"] + }, + "v1CreateTvcManifestApprovalsResult": { + "type": "object", + "properties": { + "approvalIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The unique identifier(s) for the manifest approvals" + } + }, + "required": ["approvalIds"] + }, "v1CreateUserTagIntent": { "type": "object", "properties": { @@ -6507,7 +6698,20 @@ }, "v1Curve": { "type": "string", - "enum": ["CURVE_SECP256K1", "CURVE_ED25519"] + "enum": ["CURVE_SECP256K1", "CURVE_ED25519", "CURVE_P256"] + }, + "v1CustomRevertError": { + "type": "object", + "properties": { + "errorName": { + "type": "string", + "description": "The name of the custom error." + }, + "paramsJson": { + "type": "string", + "description": "The decoded parameters as a JSON object." + } + } }, "v1DeleteApiKeysIntent": { "type": "object", @@ -7622,37 +7826,15 @@ "eip155:1", "eip155:11155111", "eip155:8453", - "eip155:84532" + "eip155:84532", + "eip155:137", + "eip155:80002" ], "description": "CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet)." } }, "required": ["signedTransaction", "caip2"] }, - "v1EthSendRawTransactionRequest": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["ACTIVITY_TYPE_ETH_SEND_RAW_TRANSACTION"] - }, - "timestampMs": { - "type": "string", - "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests." - }, - "organizationId": { - "type": "string", - "description": "Unique identifier for a given Organization." - }, - "parameters": { - "$ref": "#/definitions/v1EthSendRawTransactionIntent" - }, - "generateAppProofs": { - "type": "boolean" - } - }, - "required": ["type", "timestampMs", "organizationId", "parameters"] - }, "v1EthSendRawTransactionResult": { "type": "object", "properties": { @@ -7750,7 +7932,7 @@ "properties": { "sendTransactionStatusId": { "type": "string", - "description": "The send_transaction_status ID associated with the transaction submission for sponsored transactions" + "description": "The send_transaction_status ID associated with the transaction submission" } }, "required": ["sendTransactionStatusId"] @@ -8217,31 +8399,6 @@ }, "required": ["appProofs"] }, - "v1GetAttestationDocumentRequest": { - "type": "object", - "properties": { - "organizationId": { - "type": "string", - "description": "Unique identifier for a given organization." - }, - "enclaveType": { - "type": "string", - "description": "The enclave type, one of: ump, notarizer, signer, evm-parser." - } - }, - "required": ["organizationId", "enclaveType"] - }, - "v1GetAttestationDocumentResponse": { - "type": "object", - "properties": { - "attestationDocument": { - "type": "string", - "format": "byte", - "description": "Raw (CBOR-encoded) attestation document." - } - }, - "required": ["attestationDocument"] - }, "v1GetAuthenticatorRequest": { "type": "object", "properties": { @@ -8364,7 +8521,15 @@ }, "caip2": { "type": "string", - "description": "The network identifier in CAIP-2 format (e.g., 'eip155:1' for Ethereum mainnet)." + "enum": [ + "eip155:1", + "eip155:11155111", + "eip155:8453", + "eip155:84532", + "eip155:137", + "eip155:80002" + ], + "description": "CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet)." }, "nonce": { "type": "boolean", @@ -8491,26 +8656,6 @@ }, "required": ["configs"] }, - "v1GetOrganizationRequest": { - "type": "object", - "properties": { - "organizationId": { - "type": "string", - "description": "Unique identifier for a given organization." - } - }, - "required": ["organizationId"] - }, - "v1GetOrganizationResponse": { - "type": "object", - "properties": { - "organizationData": { - "$ref": "#/definitions/v1OrganizationData", - "description": "Object representing the full current and deleted / disabled collection of users, policies, private keys, and invitations attributable to a particular organization." - } - }, - "required": ["organizationData"] - }, "v1GetPoliciesRequest": { "type": "object", "properties": { @@ -8662,6 +8807,10 @@ "txError": { "type": "string", "description": "The error encountered when broadcasting or confirming the transaction, if any." + }, + "error": { + "$ref": "#/definitions/v1TxError", + "description": "Structured error information including revert details, if available." } }, "required": ["txStatus"] @@ -8900,26 +9049,67 @@ }, "required": ["accounts"] }, - "v1GetWalletRequest": { + "v1GetWalletAddressBalancesRequest": { "type": "object", "properties": { "organizationId": { "type": "string", "description": "Unique identifier for a given organization." }, - "walletId": { + "address": { "type": "string", - "description": "Unique identifier for a given wallet." - } - }, - "required": ["organizationId", "walletId"] - }, - "v1GetWalletResponse": { - "type": "object", - "properties": { - "wallet": { - "$ref": "#/definitions/v1Wallet", - "description": "A collection of deterministically generated cryptographic public / private key pairs that share a common seed." + "description": "Address corresponding to a wallet account." + }, + "caip2": { + "type": "string", + "enum": [ + "eip155:1", + "eip155:11155111", + "eip155:8453", + "eip155:84532", + "eip155:137", + "eip155:80002", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" + ], + "description": "CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet or 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet). Human-readable Solana aliases ('solana:mainnet', 'solana:devnet') are also accepted and normalized to canonical CAIP-2 values." + } + }, + "required": ["organizationId", "address", "caip2"] + }, + "v1GetWalletAddressBalancesResponse": { + "type": "object", + "properties": { + "balances": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1AssetBalance" + }, + "description": "List of asset balances" + } + } + }, + "v1GetWalletRequest": { + "type": "object", + "properties": { + "organizationId": { + "type": "string", + "description": "Unique identifier for a given organization." + }, + "walletId": { + "type": "string", + "description": "Unique identifier for a given wallet." + } + }, + "required": ["organizationId", "walletId"] + }, + "v1GetWalletResponse": { + "type": "object", + "properties": { + "wallet": { + "$ref": "#/definitions/v1Wallet", + "description": "A collection of deterministically generated cryptographic public / private key pairs that share a common seed." } }, "required": ["wallet"] @@ -9608,12 +9798,67 @@ }, "required": ["otpType", "contact", "appName"] }, + "v1InitOtpIntentV3": { + "type": "object", + "properties": { + "otpType": { + "type": "string", + "description": "Whether to send OTP via SMS or email. Possible values: OTP_TYPE_SMS, OTP_TYPE_EMAIL" + }, + "contact": { + "type": "string", + "description": "Email or phone number to send the OTP code to" + }, + "appName": { + "type": "string", + "description": "The name of the application." + }, + "otpLength": { + "type": "integer", + "format": "int32", + "description": "Optional length of the OTP code. Default = 9" + }, + "emailCustomization": { + "$ref": "#/definitions/v1EmailCustomizationParamsV2", + "description": "Optional parameters for customizing emails. If not provided, the default email will be used." + }, + "smsCustomization": { + "$ref": "#/definitions/v1SmsCustomizationParams", + "description": "Optional parameters for customizing SMS message. If not provided, the default sms message will be used." + }, + "userIdentifier": { + "type": "string", + "description": "Optional client-generated user identifier to enable per-user rate limiting for SMS auth. We recommend using a hash of the client-side IP address." + }, + "sendFromEmailAddress": { + "type": "string", + "description": "Optional custom email address from which to send the OTP email" + }, + "alphanumeric": { + "type": "boolean", + "description": "Optional flag to specify if the OTP code should be alphanumeric (Crockford’s Base32). If set to false, OTP code will only be numeric. Default = true" + }, + "sendFromEmailSenderName": { + "type": "string", + "description": "Optional custom sender name for use with sendFromEmailAddress; if left empty, will default to 'Notifications'" + }, + "expirationSeconds": { + "type": "string", + "description": "Expiration window (in seconds) indicating how long the OTP is valid for. If not provided, a default of 5 minutes will be used. Maximum value is 600 seconds (10 minutes)" + }, + "replyToEmailAddress": { + "type": "string", + "description": "Optional custom email address to use as reply-to" + } + }, + "required": ["otpType", "contact", "appName"] + }, "v1InitOtpRequest": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["ACTIVITY_TYPE_INIT_OTP_V2"] + "enum": ["ACTIVITY_TYPE_INIT_OTP_V3"] }, "timestampMs": { "type": "string", @@ -9624,7 +9869,7 @@ "description": "Unique identifier for a given Organization." }, "parameters": { - "$ref": "#/definitions/v1InitOtpIntentV2" + "$ref": "#/definitions/v1InitOtpIntentV3" }, "generateAppProofs": { "type": "boolean" @@ -9642,6 +9887,20 @@ }, "required": ["otpId"] }, + "v1InitOtpResultV2": { + "type": "object", + "properties": { + "otpId": { + "type": "string", + "description": "Unique identifier for an OTP flow" + }, + "otpEncryptionTargetBundle": { + "type": "string", + "description": "Signed bundle containing a target encryption key to use when submitting OTP codes." + } + }, + "required": ["otpId", "otpEncryptionTargetBundle"] + }, "v1InitUserEmailRecoveryIntent": { "type": "object", "properties": { @@ -10085,61 +10344,32 @@ }, "upsertGasUsageConfigIntent": { "$ref": "#/definitions/v1UpsertGasUsageConfigIntent" - } - } - }, - "v1Invitation": { - "type": "object", - "properties": { - "invitationId": { - "type": "string", - "description": "Unique identifier for a given Invitation object." }, - "receiverUserName": { - "type": "string", - "description": "The name of the intended Invitation recipient." + "createTvcAppIntent": { + "$ref": "#/definitions/v1CreateTvcAppIntent" }, - "receiverEmail": { - "type": "string", - "description": "The email address of the intended Invitation recipient." + "createTvcDeploymentIntent": { + "$ref": "#/definitions/v1CreateTvcDeploymentIntent" }, - "receiverUserTags": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of tags assigned to the Invitation recipient." + "createTvcManifestApprovalsIntent": { + "$ref": "#/definitions/v1CreateTvcManifestApprovalsIntent" }, - "accessType": { - "$ref": "#/definitions/v1AccessType", - "description": "The User's permissible access method(s)." + "solSendTransactionIntent": { + "$ref": "#/definitions/v1SolSendTransactionIntent" }, - "status": { - "$ref": "#/definitions/v1InvitationStatus", - "description": "The current processing status of a specified Invitation." + "initOtpIntentV3": { + "$ref": "#/definitions/v1InitOtpIntentV3" }, - "createdAt": { - "$ref": "#/definitions/externaldatav1Timestamp" + "verifyOtpIntentV2": { + "$ref": "#/definitions/v1VerifyOtpIntentV2" }, - "updatedAt": { - "$ref": "#/definitions/externaldatav1Timestamp" + "otpLoginIntentV2": { + "$ref": "#/definitions/v1OtpLoginIntentV2" }, - "senderUserId": { - "type": "string", - "description": "Unique identifier for the Sender of an Invitation." + "updateOrganizationNameIntent": { + "$ref": "#/definitions/v1UpdateOrganizationNameIntent" } - }, - "required": [ - "invitationId", - "receiverUserName", - "receiverEmail", - "receiverUserTags", - "accessType", - "status", - "createdAt", - "updatedAt", - "senderUserId" - ] + } }, "v1InvitationParams": { "type": "object", @@ -10176,14 +10406,6 @@ "senderUserId" ] }, - "v1InvitationStatus": { - "type": "string", - "enum": [ - "INVITATION_STATUS_CREATED", - "INVITATION_STATUS_ACCEPTED", - "INVITATION_STATUS_REVOKED" - ] - }, "v1ListFiatOnRampCredentialsRequest": { "type": "object", "properties": { @@ -10254,6 +10476,43 @@ }, "required": ["privateKeyTags"] }, + "v1ListSupportedAssetsRequest": { + "type": "object", + "properties": { + "organizationId": { + "type": "string", + "description": "Unique identifier for a given organization." + }, + "caip2": { + "type": "string", + "enum": [ + "eip155:1", + "eip155:11155111", + "eip155:8453", + "eip155:84532", + "eip155:137", + "eip155:80002", + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" + ], + "description": "CAIP-2 chain ID (e.g., 'eip155:1' for Ethereum mainnet or 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet). Human-readable Solana aliases ('solana:mainnet', 'solana:devnet') are also accepted and normalized to canonical CAIP-2 values." + } + }, + "required": ["organizationId", "caip2"] + }, + "v1ListSupportedAssetsResponse": { + "type": "object", + "properties": { + "assets": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1AssetMetadata" + }, + "description": "List of asset metadata" + } + } + }, "v1ListUserTagsRequest": { "type": "object", "properties": { @@ -10314,6 +10573,24 @@ }, "required": ["stamp"] }, + "v1NativeRevertError": { + "type": "object", + "properties": { + "nativeType": { + "type": "string", + "description": "The type of native error: 'error_string', 'panic', or 'execution_reverted'." + }, + "message": { + "type": "string", + "description": "The error message for Error(string) reverts." + }, + "panicCode": { + "type": "string", + "format": "uint64", + "description": "The panic code for Panic(uint256) reverts." + } + } + }, "v1Oauth2AuthenticateIntent": { "type": "object", "properties": { @@ -10621,76 +10898,6 @@ "OPERATOR_CONTAINS_ALL" ] }, - "v1OrganizationData": { - "type": "object", - "properties": { - "organizationId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "users": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1User" - } - }, - "policies": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1Policy" - } - }, - "privateKeys": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1PrivateKey" - } - }, - "invitations": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1Invitation" - } - }, - "tags": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/datav1Tag" - } - }, - "rootQuorum": { - "$ref": "#/definitions/externaldatav1Quorum" - }, - "features": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1Feature" - } - }, - "wallets": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1Wallet" - } - }, - "smartContractInterfaceReferences": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1SmartContractInterfaceReference" - } - } - } - }, "v1OtpAuthIntent": { "type": "object", "properties": { @@ -10789,12 +10996,38 @@ }, "required": ["verificationToken", "publicKey"] }, + "v1OtpLoginIntentV2": { + "type": "object", + "properties": { + "verificationToken": { + "type": "string", + "description": "Signed Verification Token containing a unique id, expiry, verification type, contact" + }, + "publicKey": { + "type": "string", + "description": "Client-side public key generated by the user, used as the session public key upon successful login" + }, + "clientSignature": { + "$ref": "#/definitions/v1ClientSignature", + "description": "Required signature proving authorization for this login. The signature is over the verification token ID and the public key. Required for secure OTP login process." + }, + "expirationSeconds": { + "type": "string", + "description": "Expiration window (in seconds) indicating how long the Session is valid for. If not provided, a default of 15 minutes will be used." + }, + "invalidateExisting": { + "type": "boolean", + "description": "Invalidate all other previously generated Login sessions" + } + }, + "required": ["verificationToken", "publicKey", "clientSignature"] + }, "v1OtpLoginRequest": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["ACTIVITY_TYPE_OTP_LOGIN"] + "enum": ["ACTIVITY_TYPE_OTP_LOGIN_V2"] }, "timestampMs": { "type": "string", @@ -10805,7 +11038,7 @@ "description": "Unique identifier for a given Organization." }, "parameters": { - "$ref": "#/definitions/v1OtpLoginIntent" + "$ref": "#/definitions/v1OtpLoginIntentV2" }, "generateAppProofs": { "type": "boolean" @@ -11463,6 +11696,53 @@ }, "upsertGasUsageConfigResult": { "$ref": "#/definitions/v1UpsertGasUsageConfigResult" + }, + "createTvcAppResult": { + "$ref": "#/definitions/v1CreateTvcAppResult" + }, + "createTvcDeploymentResult": { + "$ref": "#/definitions/v1CreateTvcDeploymentResult" + }, + "createTvcManifestApprovalsResult": { + "$ref": "#/definitions/v1CreateTvcManifestApprovalsResult" + }, + "solSendTransactionResult": { + "$ref": "#/definitions/v1SolSendTransactionResult" + }, + "initOtpResultV2": { + "$ref": "#/definitions/v1InitOtpResultV2" + }, + "updateOrganizationNameResult": { + "$ref": "#/definitions/v1UpdateOrganizationNameResult" + } + } + }, + "v1RevertChainEntry": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "The contract address where the revert occurred." + }, + "errorType": { + "type": "string", + "description": "Type of error: 'unknown', 'native', or 'custom'." + }, + "displayMessage": { + "type": "string", + "description": "Human-readable message describing this revert." + }, + "unknown": { + "$ref": "#/definitions/v1UnknownRevertError", + "description": "Details for unknown error types." + }, + "native": { + "$ref": "#/definitions/v1NativeRevertError", + "description": "Details for native Solidity errors (Error, Panic, execution reverted)." + }, + "custom": { + "$ref": "#/definitions/v1CustomRevertError", + "description": "Details for custom contract errors." } } }, @@ -11957,20 +12237,6 @@ } } }, - "v1SmartContractInterfaceReference": { - "type": "object", - "properties": { - "smartContractInterfaceId": { - "type": "string" - }, - "smartContractAddress": { - "type": "string" - }, - "digest": { - "type": "string" - } - } - }, "v1SmartContractInterfaceType": { "type": "string", "enum": [ @@ -11987,6 +12253,71 @@ } } }, + "v1SolSendTransactionIntent": { + "type": "object", + "properties": { + "unsignedTransaction": { + "type": "string", + "description": "Base64-encoded serialized unsigned Solana transaction" + }, + "signWith": { + "type": "string", + "description": "A wallet or private key address to sign with. This does not support private key IDs." + }, + "sponsor": { + "type": "boolean", + "description": "Whether to sponsor this transaction via Gas Station." + }, + "caip2": { + "type": "string", + "enum": [ + "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", + "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY" + ], + "description": "CAIP-2 chain ID (e.g., 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' for Solana mainnet)." + }, + "recentBlockhash": { + "type": "string", + "description": "user-provided blockhash for replay protection / deadline control. If omitted and sponsor=true, we fetch a fresh blockhash during execution" + } + }, + "required": ["unsignedTransaction", "signWith", "caip2"] + }, + "v1SolSendTransactionRequest": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["ACTIVITY_TYPE_SOL_SEND_TRANSACTION"] + }, + "timestampMs": { + "type": "string", + "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests." + }, + "organizationId": { + "type": "string", + "description": "Unique identifier for a given Organization." + }, + "parameters": { + "$ref": "#/definitions/v1SolSendTransactionIntent" + }, + "generateAppProofs": { + "type": "boolean" + } + }, + "required": ["type", "timestampMs", "organizationId", "parameters"] + }, + "v1SolSendTransactionResult": { + "type": "object", + "properties": { + "sendTransactionStatusId": { + "type": "string", + "description": "The send_transaction_status ID associated with the transaction submission" + } + }, + "required": ["sendTransactionStatusId"] + }, "v1StampLoginIntent": { "type": "object", "properties": { @@ -12043,28 +12374,6 @@ "type": "string", "enum": ["TAG_TYPE_USER", "TAG_TYPE_PRIVATE_KEY"] }, - "v1TestRateLimitsRequest": { - "type": "object", - "properties": { - "organizationId": { - "type": "string", - "description": "Unique identifier for a given organization. If the request is being made by a WebAuthN user and their sub-organization ID is unknown, this can be the parent organization ID; using the sub-organization ID when possible is preferred due to performance reasons." - }, - "isSetLimit": { - "type": "boolean", - "description": "Whether or not to set a limit on this request." - }, - "limit": { - "type": "integer", - "format": "int64", - "description": "Rate limit to set for org, if is_set_limit is set to true." - } - }, - "required": ["organizationId", "isSetLimit", "limit"] - }, - "v1TestRateLimitsResponse": { - "type": "object" - }, "v1TokenUsage": { "type": "object", "properties": { @@ -12091,9 +12400,98 @@ "TRANSACTION_TYPE_ETHEREUM", "TRANSACTION_TYPE_SOLANA", "TRANSACTION_TYPE_TRON", - "TRANSACTION_TYPE_BITCOIN" + "TRANSACTION_TYPE_BITCOIN", + "TRANSACTION_TYPE_TEMPO" ] }, + "v1TvcManifestApproval": { + "type": "object", + "properties": { + "operatorId": { + "type": "string", + "description": "Unique identifier of the operator providing this approval" + }, + "signature": { + "type": "string", + "description": "Signature from the operator approving the manifest" + } + }, + "required": ["operatorId", "signature"] + }, + "v1TvcOperatorParams": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name for this new operator" + }, + "publicKey": { + "type": "string", + "description": "Public key for this operator" + } + }, + "required": ["name", "publicKey"] + }, + "v1TvcOperatorSetParams": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Short description for this new operator set" + }, + "newOperators": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1TvcOperatorParams" + }, + "description": "Operators to create as part of this new operator set" + }, + "existingOperatorIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Existing operators to use as part of this new operator set" + }, + "threshold": { + "type": "integer", + "format": "int64", + "description": "The threshold of operators needed to reach consensus in this new Operator Set" + } + }, + "required": ["name", "threshold"] + }, + "v1TxError": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Human-readable error message describing what went wrong." + }, + "revertChain": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1RevertChainEntry" + }, + "description": "Chain of revert errors from nested contract calls, ordered from outermost to innermost." + } + } + }, + "v1UnknownRevertError": { + "type": "object", + "properties": { + "selector": { + "type": "string", + "description": "The 4-byte error selector, if available." + }, + "data": { + "type": "string", + "description": "The raw error data, hex-encoded." + } + } + }, "v1UpdateAllowedOriginsIntent": { "type": "object", "properties": { @@ -12186,6 +12584,13 @@ "verificationTokenRequiredForGetAccountPii": { "type": "boolean", "description": "Verification token required for get account with PII (email/phone number). Default false." + }, + "socialLinkingClientIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Whitelisted OAuth client IDs for social account linking. When a user authenticates via a social provider with an email matching an existing account, the accounts will be linked if the client ID is in this list and the issuer is considered a trusted provider." } } }, @@ -12328,6 +12733,51 @@ }, "required": ["oauth2CredentialId"] }, + "v1UpdateOrganizationNameIntent": { + "type": "object", + "properties": { + "organizationName": { + "type": "string", + "description": "New name for the Organization." + } + }, + "required": ["organizationName"] + }, + "v1UpdateOrganizationNameRequest": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["ACTIVITY_TYPE_UPDATE_ORGANIZATION_NAME"] + }, + "timestampMs": { + "type": "string", + "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests." + }, + "organizationId": { + "type": "string", + "description": "Unique identifier for a given Organization." + }, + "parameters": { + "$ref": "#/definitions/v1UpdateOrganizationNameIntent" + } + }, + "required": ["type", "timestampMs", "organizationId", "parameters"] + }, + "v1UpdateOrganizationNameResult": { + "type": "object", + "properties": { + "organizationId": { + "type": "string", + "description": "Unique identifier for the Organization." + }, + "organizationName": { + "type": "string", + "description": "The updated organization name." + } + }, + "required": ["organizationId", "organizationName"] + }, "v1UpdatePolicyIntent": { "type": "object", "properties": { @@ -12878,6 +13328,10 @@ "windowDurationMinutes": { "type": "string", "description": "Rolling sponsorship window duration, expressed in minutes." + }, + "enabled": { + "type": "boolean", + "description": "Whether gas sponsorship is enabled for the organization." } }, "required": [ @@ -13129,12 +13583,30 @@ }, "required": ["otpId", "otpCode"] }, + "v1VerifyOtpIntentV2": { + "type": "object", + "properties": { + "otpId": { + "type": "string", + "description": "UUID representing an OTP flow. A new UUID is created for each init OTP activity." + }, + "encryptedOtpBundle": { + "type": "string", + "description": "Encrypted bundle containing the OTP code and a client-generated public key. Turnkey's secure enclaves will decrypt this bundle, verify the OTP code, and issue a new Verification Token. Encrypted using the target encryption key provided in the INIT_OTP activity result." + }, + "expirationSeconds": { + "type": "string", + "description": "Expiration window (in seconds) indicating how long the verification token is valid for. If not provided, a default of 1 hour will be used. Maximum value is 86400 seconds (24 hours)" + } + }, + "required": ["otpId", "encryptedOtpBundle"] + }, "v1VerifyOtpRequest": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["ACTIVITY_TYPE_VERIFY_OTP"] + "enum": ["ACTIVITY_TYPE_VERIFY_OTP_V2"] }, "timestampMs": { "type": "string", @@ -13145,7 +13617,7 @@ "description": "Unique identifier for a given Organization." }, "parameters": { - "$ref": "#/definitions/v1VerifyOtpIntent" + "$ref": "#/definitions/v1VerifyOtpIntentV2" }, "generateAppProofs": { "type": "boolean"