kokio-sdk 0.0.10 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,45 @@
1
1
  name: Publish SDK
2
2
 
3
3
  on:
4
- # Publishing only happens when pushing to publish branch is made
5
4
  push:
6
5
  branches: [ "main" ]
7
6
 
7
+ permissions:
8
+ id-token: write
9
+ contents: write
10
+
8
11
  jobs:
9
12
  publish-npm:
13
+ if: contains(github.event.head_commit.message, 'Release')
10
14
  runs-on: ubuntu-latest
15
+
11
16
  steps:
12
17
  - uses: actions/checkout@v4
18
+
13
19
  - uses: actions/setup-node@v4
14
20
  with:
15
21
  node-version: 22
16
22
  registry-url: https://registry.npmjs.org/
17
- - run: npm publish --access public
18
- env:
19
- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
23
+
24
+ - name: Get version from package.json
25
+ id: pkg
26
+ run: |
27
+ VERSION=$(node -p "require('./package.json').version")
28
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
29
+
30
+ - name: Create Git tag
31
+ run: |
32
+ git tag v${{ steps.pkg.outputs.version }}
33
+ git push origin v${{ steps.pkg.outputs.version }}
34
+
35
+ - name: Install deps (also runs postinstall)
36
+ run: npm install
37
+
38
+ - name: Publish to npm
39
+ run: npm publish --access public
40
+
41
+ - name: Create GitHub Release
42
+ uses: softprops/action-gh-release@v2
43
+ with:
44
+ tag_name: v${{ steps.pkg.outputs.version }}
45
+ generate_release_notes: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kokio-sdk",
3
- "version": "0.0.10",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/config.js",
@@ -42,8 +42,6 @@
42
42
  "@peculiar/asn1-ecc": "^2.3.15",
43
43
  "@peculiar/asn1-schema": "^2.3.15",
44
44
  "@simplewebauthn/server": "^13.1.1",
45
- "@turnkey/http": "^3.0.0",
46
- "@turnkey/react-native-passkey-stamper": "^1.0.13",
47
45
  "ethers": "^6.13.5",
48
46
  "react-native-passkey": "^3.1.0",
49
47
  "viem": "^2.23.5"
package/src/config.ts CHANGED
@@ -8,11 +8,9 @@ import { DeviceWalletSubPackage } from "./interface/deviceWalletClass.js";
8
8
  import { ESIMWalletSubPackage } from "./interface/eSIMWalletClass.js";
9
9
  import { ESIMWalletFactorySubPackage } from "./interface/eSIMWalletFactoryClass.js";
10
10
  import { SmartAccountClient } from "@aa-sdk/core";
11
- import { TurnkeyClient } from "@turnkey/http";
12
11
 
13
12
  export class Kokio {
14
13
  viemWalletClient: WalletClient;
15
- turnkeyClient: TurnkeyClient;
16
14
  credentialId: string;
17
15
  rpId: string;
18
16
  organizationId: string;
@@ -31,7 +29,6 @@ export class Kokio {
31
29
 
32
30
  constructor(
33
31
  viemWalletClient: WalletClient,
34
- turnkeyClient: TurnkeyClient,
35
32
  credentialId: string,
36
33
  rpId: string,
37
34
  organizationId: string,
@@ -42,7 +39,6 @@ export class Kokio {
42
39
  eSIMWalletAddress?: Address
43
40
  ) {
44
41
  this.viemWalletClient = viemWalletClient;
45
- this.turnkeyClient = turnkeyClient;
46
42
  this.credentialId = credentialId;
47
43
  this.rpId = rpId;
48
44
  this.organizationId = organizationId;
@@ -51,7 +47,7 @@ export class Kokio {
51
47
 
52
48
  this.constants = new ConstantsSubPackage(this.viemWalletClient, this.pimlicoAPIKey);
53
49
 
54
- this.smartAccount = new SmartAccountSubPackage(this.viemWalletClient, this.turnkeyClient, this.credentialId, this.rpId, this.organizationId, this.pimlicoAPIKey, this.gasPolicyId);
50
+ this.smartAccount = new SmartAccountSubPackage(this.viemWalletClient, this.credentialId, this.rpId, this.organizationId, this.pimlicoAPIKey, this.gasPolicyId);
55
51
  this.deviceWalletFactory = smartAccountClient? new DeviceWalletFactorySubPackage(viemWalletClient, smartAccountClient): undefined;
56
52
  this.eSIMWalletFactory = smartAccountClient? new ESIMWalletFactorySubPackage(viemWalletClient, smartAccountClient): undefined;
57
53
  this.lazyWalletRegistry = smartAccountClient? new LazyWalletRegistrySubPackage(smartAccountClient): undefined;
@@ -2,21 +2,18 @@ import { Address, WalletClient } from "viem";
2
2
  import { _getSmartWallet, _getSmartWalletClient } from "../logic/account-kit/createSmartAccount.js";
3
3
  import { P256Key, SignedRequest } from "../types";
4
4
  import { SmartAccountClient, SmartContractAccount } from "@aa-sdk/core";
5
- import { TurnkeyClient } from "@turnkey/http";
6
5
 
7
6
  export class SmartAccountSubPackage {
8
7
 
9
8
  client;
10
- turnkeyClient;
11
9
  credentialId;
12
10
  rpId;
13
11
  organizationId;
14
12
  pimlicoAPIKey;
15
13
  gasPolicyId;
16
14
 
17
- constructor(client: WalletClient, turnkeyClient: TurnkeyClient, credentialId: string, rpId: string, organiationId: string, pimlicoAPIKey: string, gasPolicyId: string) {
15
+ constructor(client: WalletClient, credentialId: string, rpId: string, organiationId: string, pimlicoAPIKey: string, gasPolicyId: string) {
18
16
  this.client = client;
19
- this.turnkeyClient = turnkeyClient;
20
17
  this.credentialId = credentialId;
21
18
  this.rpId = rpId;
22
19
  this.organizationId = organiationId;
@@ -25,7 +22,7 @@ export class SmartAccountSubPackage {
25
22
  }
26
23
 
27
24
  getSmartWallet (deviceUniqueIdentifier: string, deviceWalletOwnerKey: P256Key, salt: bigint) {
28
- return _getSmartWallet(this.client, this.turnkeyClient, this.credentialId, this.rpId, this.organizationId, deviceUniqueIdentifier, deviceWalletOwnerKey, salt);
25
+ return _getSmartWallet(this.client, this.credentialId, this.rpId, this.organizationId, deviceUniqueIdentifier, deviceWalletOwnerKey, salt);
29
26
  }
30
27
 
31
28
  getSmartWalletClient (account: SmartContractAccount) {
@@ -1,38 +1,113 @@
1
1
  import {
2
- AccountOp,
3
- createSmartAccountClient,
4
- getEntryPoint,
5
- SmartContractAccount,
6
- toSmartContractAccount,
7
- split,
8
- SmartAccountClient
2
+ AccountOp, createSmartAccountClient, getEntryPoint, SmartContractAccount,
3
+ toSmartContractAccount, split, SmartAccountClient
9
4
  } from "@aa-sdk/core";
10
5
  import {
11
- http,
12
- type SignableMessage,
13
- type Hash,
14
- WalletClient,
15
- Hex,
16
- encodeFunctionData,
17
- Address,
18
- encodePacked,
19
- encodeAbiParameters,
20
- getContract,
21
- TypedDataDefinition,
22
- TypedData,
23
- hashMessage,
24
- stringToBytes,
25
- toHex
6
+ http, type SignableMessage, type Hash, WalletClient, Hex, encodeFunctionData,
7
+ Address, encodePacked, encodeAbiParameters, getContract,
8
+ TypedDataDefinition, TypedData, hashMessage, stringToBytes, toHex, hashTypedData, hexToBytes, bytesToHex
26
9
  } from "viem";
27
- import { TurnkeyClient } from "@turnkey/http";
28
10
  import { BytesLike, ethers, hexlify, toBeHex, toBigInt, zeroPadValue } from "ethers";
29
11
  import { _getChainSpecificConstants, ZERO, SIGNATURE_VALIDITY_SECONDS } from "../constants.js";
30
12
  import { _add0x, _concatUint8Arrays, _remove0x, _shouldRemoveLeadingZero } from "../utils.js";
31
13
  import { P256Key, WebAuthnSignature } from "../../types.js";
32
14
  import { DeviceWallet, DeviceWalletFactory } from "../../abis/index.js";
33
- import { _signMessageWithTurnkey, _signTypedDataWithTurnkey, _stamp, _stampAndSignMessageWithTurnkey, _stampAndSignTypedDataWithTurnkey } from "../services/turnkeyClient.js";
34
15
  import { alchemyGasManagerMiddleware } from "@account-kit/infra";
35
16
 
17
+ import { decodeAttestationObject, decodeClientDataJSON, isoBase64URL, parseAuthenticatorData } from "@simplewebauthn/server/helpers";
18
+ import { Passkey, PasskeyGetRequest, PasskeyGetResult } from "react-native-passkey";
19
+ import { p256 } from "@noble/curves/nist.js";
20
+
21
+ type BrokenPasskeyGetResult = PasskeyGetResult | string;
22
+
23
+ enum AuthenticatorTransport {
24
+ usb = "usb",
25
+ nfc = "nfc",
26
+ ble = "ble",
27
+ smartCard = "smart-card",
28
+ hybrid = "hybrid",
29
+ internal = "internal"
30
+ }
31
+
32
+ /*
33
+ ** Stamp is client-side authentication. Since the passkeys are one the user's mobile device
34
+ ** react-native-passkey helps fetch passkey for the user (provided credentialId, rpId).
35
+ ** This is exactly how the WebAuthn.sol contract needs it to be.
36
+ */
37
+ export const _stamp = async (credentialId: string, rpId: string, payload: Hex): Promise<WebAuthnSignature> => {
38
+ const signingOptions: PasskeyGetRequest = {
39
+ challenge: isoBase64URL.fromBuffer(
40
+ (hexToBytes(payload) as unknown) as Uint8Array<ArrayBuffer>
41
+ ),
42
+ allowCredentials: [{
43
+ id: credentialId,
44
+ type: "public-key",
45
+ transports: [AuthenticatorTransport.internal]
46
+ }],
47
+ rpId,
48
+ userVerification: "required"
49
+ };
50
+
51
+ let authenticationResult;
52
+ try {
53
+ authenticationResult = await Passkey.get(signingOptions);
54
+ } catch (e) {
55
+ console.log("Failed to get authenticationResult");
56
+ console.error(JSON.stringify(e, Object.getOwnPropertyNames(e)))
57
+ }
58
+
59
+ // See https://github.com/f-23/react-native-passkey/issues/54
60
+ // On Android the typedef lies. Authentication result is actually a string!
61
+ // TODO: remove me once the above is resolved.
62
+ const brokenAuthenticationResult =
63
+ authenticationResult as BrokenPasskeyGetResult;
64
+ if (typeof brokenAuthenticationResult === "string") {
65
+ authenticationResult = JSON.parse(brokenAuthenticationResult);
66
+ }
67
+
68
+ const { clientDataJSON, authenticatorData, signature } = authenticationResult.response;
69
+
70
+ // 1. Decode clientDataJSON
71
+ const clientDataJSONBuffer = isoBase64URL.toBuffer(clientDataJSON);
72
+ const clientDataJSONString = new TextDecoder().decode(clientDataJSONBuffer);
73
+
74
+ // 2. Calculate indices for the contract (byte offsets)
75
+ const typeSearchString = '"type":"webauthn.get"';
76
+ const challengeSearchString = '"challenge":';
77
+
78
+ const typeIndex = clientDataJSONString.indexOf(typeSearchString);
79
+ const challengeIndex = clientDataJSONString.indexOf(challengeSearchString);
80
+
81
+ if (typeIndex === -1) {
82
+ console.warn(`Warning: Could not find type substring '${typeSearchString}' in clientDataJSON. Setting typeIndex to 0.`);
83
+ }
84
+ if (challengeIndex === -1) {
85
+ throw new Error(`Could not find challenge substring '${challengeSearchString}' in clientDataJSON for index calculation.`);
86
+ }
87
+
88
+ // 3. Decode authenticatorData
89
+ const authenticatorDataBytes = isoBase64URL.toBuffer(authenticatorData);
90
+ const authenticatorDataHex = bytesToHex(authenticatorDataBytes);
91
+
92
+ // 4. Decode signature (ASN.1 DER encoded)
93
+ const signatureBytes = isoBase64URL.toBuffer(signature);
94
+ let parsedSignature = p256.Signature.fromDER(signatureBytes);
95
+ parsedSignature = parsedSignature.normalizeS();
96
+ let r = parsedSignature.r;
97
+ let s = parsedSignature.s;
98
+
99
+ const webAuthnSig = {
100
+ authenticatorData: authenticatorDataHex,
101
+ clientDataJSON: clientDataJSONString,
102
+ challengeIndex: BigInt(challengeIndex),
103
+ typeIndex: BigInt(typeIndex),
104
+ r: r,
105
+ s: s
106
+ };
107
+
108
+ return webAuthnSig;
109
+ }
110
+
36
111
  const _encodeExecute = async (tx: AccountOp) => {
37
112
 
38
113
  return encodeFunctionData({
@@ -177,7 +252,7 @@ const _signMessage = async (message: SignableMessage, credentialId: string, rpId
177
252
  const payload = hashMessage({raw: stringToBytes(message as string)});
178
253
  // The original message is passed to the stamp and sign function.
179
254
  // The stamp and sign function creates the EIP-191 digest hash using its hashMessage function
180
- // The result of the hashMessage(message) will be the `payload` used by Turnkey as a challenge
255
+ // The result of the hashMessage(message) will be the `payload` used as a challenge
181
256
  const webAuthnSignature = await _stamp(credentialId, rpId, payload);
182
257
 
183
258
  return _encodeSignature(webAuthnSignature, validUntil);
@@ -186,12 +261,20 @@ const _signMessage = async (message: SignableMessage, credentialId: string, rpId
186
261
  const _signTypedData = async <
187
262
  const typedData extends TypedData | Record<string, unknown>,
188
263
  primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
189
- > (typedData: TypedDataDefinition<typedData, primaryType>, turnkeyClient: TurnkeyClient, organiationId: string, signWith: Address): Promise<Hex> => {
264
+ > (typedData: TypedDataDefinition<typedData, primaryType>, organiationId: string, signWith: Address): Promise<Hex> => {
190
265
 
191
266
  // signature valid until, UNIX timestamp in seconds
192
267
  const validUntil = Math.floor(Date.now() / 1000) + SIGNATURE_VALIDITY_SECONDS;
193
268
 
194
- const webAuthnSignature = await _stampAndSignTypedDataWithTurnkey(turnkeyClient, organiationId, signWith, typedData);
269
+ // TODO: Implement stamping for Typed Data
270
+ let webAuthnSignature = {
271
+ authenticatorData: `0x` as `0x${string}`,
272
+ clientDataJSON: "",
273
+ challengeIndex: BigInt(0),
274
+ typeIndex: BigInt(0),
275
+ r: BigInt(0),
276
+ s: BigInt(0)
277
+ }
195
278
 
196
279
  return _encodeSignature(webAuthnSignature, validUntil);
197
280
  }
@@ -215,7 +298,6 @@ const _signUserOperationHash = async (credentialId: string, rpId: string, userOp
215
298
 
216
299
  export const _getSmartWallet = async (
217
300
  client: WalletClient,
218
- turnkeyClient: TurnkeyClient,
219
301
  credentialId: string,
220
302
  rpId: string,
221
303
  organiationId: string,
@@ -253,7 +335,7 @@ export const _getSmartWallet = async (
253
335
 
254
336
  signMessage: async ({ message}): Promise<Hash> => _signMessage(message, credentialId, rpId),
255
337
 
256
- signTypedData: async (typedData): Promise<Hash> => _signTypedData(typedData, turnkeyClient, organiationId, signWith),
338
+ signTypedData: async (typedData): Promise<Hash> => _signTypedData(typedData, organiationId, signWith),
257
339
 
258
340
  /// OPTIONAL PARAMS ///
259
341
  // if you already know your account's address, pass that in here to avoid generating a new counterfactual
@@ -1,296 +0,0 @@
1
- import { assertActivityCompleted, assertNonNull, TurnkeyClient, TActivityResponse } from "@turnkey/http"
2
- import { Address, hashMessage, hashTypedData, Hex, hexToBytes, SignableMessage, toHex, TypedData, TypedDataDefinition, bytesToHex } from "viem";
3
- import { decodeAttestationObject, decodeClientDataJSON, isoBase64URL, parseAuthenticatorData } from "@simplewebauthn/server/helpers";
4
- import { _add0x, base64UrlToBuffer, parseSignature } from "../utils.js";
5
- import { WebAuthnSignature } from "../../types.js";
6
- import { Passkey, PasskeyGetResult } from "react-native-passkey";
7
- import { p256 } from "@noble/curves/nist.js";
8
- import { AuthenticatorTransport } from "@turnkey/react-native-passkey-stamper";
9
-
10
- type BrokenPasskeyGetResult = PasskeyGetResult | string;
11
-
12
- /*
13
- ** Stamp is client-side authentication. Since the passkeys are one the user's mobile device
14
- ** react-native-passkey helps fetch passkey for the user (provided credentialId, rpId).
15
- ** This is exactly how the WebAuthn.sol contract needs it to be.
16
- */
17
- export const _stamp = async (credentialId: string, rpId: string, payload: Hex): Promise<WebAuthnSignature> => {
18
- const signingOptions = {
19
- challenge: isoBase64URL.fromBuffer((hexToBytes(payload) as unknown) as Uint8Array<ArrayBuffer> ), // Base64URL of the raw EIP-191 hash bytes
20
- allowCredentials: [{
21
- id: credentialId,
22
- type: 'public-key',
23
- transports: [AuthenticatorTransport.internal]
24
- }],
25
- rpId: rpId,
26
- userVerification: "required"
27
- };
28
-
29
- let authenticationResult;
30
- try {
31
- authenticationResult = await Passkey.get(signingOptions);
32
- } catch (e) {
33
- console.log("Failed to get authenticationResult");
34
- console.error(JSON.stringify(e, Object.getOwnPropertyNames(e)))
35
- }
36
-
37
- // See https://github.com/f-23/react-native-passkey/issues/54
38
- // On Android the typedef lies. Authentication result is actually a string!
39
- // TODO: remove me once the above is resolved.
40
- const brokenAuthenticationResult =
41
- authenticationResult as BrokenPasskeyGetResult;
42
- if (typeof brokenAuthenticationResult === "string") {
43
- authenticationResult = JSON.parse(brokenAuthenticationResult);
44
- }
45
-
46
- const { clientDataJSON, authenticatorData, signature } = authenticationResult.response;
47
-
48
- // 1. Decode clientDataJSON
49
- const clientDataJSONBuffer = isoBase64URL.toBuffer(clientDataJSON);
50
- const clientDataJSONString = new TextDecoder().decode(clientDataJSONBuffer);
51
-
52
- // 2. Calculate indices for the contract (byte offsets)
53
- const typeSearchString = '"type":"webauthn.get"';
54
- const challengeSearchString = '"challenge":';
55
-
56
- const typeIndex = clientDataJSONString.indexOf(typeSearchString);
57
- const challengeIndex = clientDataJSONString.indexOf(challengeSearchString);
58
-
59
- if (typeIndex === -1) {
60
- console.warn(`Warning: Could not find type substring '${typeSearchString}' in clientDataJSON. Setting typeIndex to 0.`);
61
- }
62
- if (challengeIndex === -1) {
63
- throw new Error(`Could not find challenge substring '${challengeSearchString}' in clientDataJSON for index calculation.`);
64
- }
65
-
66
- // 3. Decode authenticatorData
67
- const authenticatorDataBytes = isoBase64URL.toBuffer(authenticatorData);
68
- const authenticatorDataHex = bytesToHex(authenticatorDataBytes);
69
-
70
- // 4. Decode signature (ASN.1 DER encoded)
71
- const signatureBytes = isoBase64URL.toBuffer(signature);
72
- let parsedSignature = p256.Signature.fromDER(signatureBytes);
73
- parsedSignature = parsedSignature.normalizeS();
74
- let r = parsedSignature.r;
75
- let s = parsedSignature.s;
76
-
77
- const webAuthnSig = {
78
- authenticatorData: authenticatorDataHex,
79
- clientDataJSON: clientDataJSONString,
80
- challengeIndex: BigInt(challengeIndex),
81
- typeIndex: BigInt(typeIndex),
82
- r: r,
83
- s: s
84
- };
85
-
86
- return webAuthnSig;
87
- }
88
-
89
- const getBytesFromPayload = (payloadHex: Hex): Uint8Array => {
90
- return hexToBytes(payloadHex);
91
- };
92
-
93
- export const _signMessageWithTurnkey = async (client: TurnkeyClient, organizationId: string, signWith: Address, message: SignableMessage): Promise<WebAuthnSignature> => {
94
-
95
- const payload = hashMessage(message);
96
-
97
- const { activity } = await client.signRawPayload({
98
- type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
99
- organizationId: organizationId,
100
- parameters: {
101
- signWith: signWith,
102
- payload: payload,
103
- encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
104
- hashFunction: "HASH_FUNCTION_NO_OP",
105
- },
106
- timestampMs: String(Date.now()), // millisecond timestamp
107
- });
108
-
109
- assertActivityCompleted(activity);
110
-
111
- const attestationObject = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.attestationObject);
112
- const decodedAttestationObj = decodeAttestationObject((base64UrlToBuffer(attestationObject) as unknown) as Uint8Array<ArrayBuffer>)
113
-
114
- const clientDataJson = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.clientDataJson);
115
- const decodedClientDataJson = decodeClientDataJSON(clientDataJson);
116
-
117
- const signature = assertNonNull(activity.result.signRawPayloadResult);
118
-
119
- return {
120
- authenticatorData: toHex(decodedAttestationObj.get("authData")),
121
- clientDataJSON: JSON.stringify(decodedClientDataJson),
122
- challengeIndex: BigInt(23),
123
- typeIndex: BigInt(1),
124
- r: BigInt(signature.r),
125
- s: BigInt(signature.s)
126
- }
127
- }
128
-
129
- export const _signTypedDataWithTurnkey = async <
130
- const typedData extends TypedData | Record<string, unknown>,
131
- primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
132
- > (client: TurnkeyClient, organizationId: string, signWith: Address, typedData: TypedDataDefinition<typedData, primaryType>): Promise<WebAuthnSignature> => {
133
-
134
- const payload = hashTypedData(typedData);
135
-
136
- const { activity } = await client.signRawPayload({
137
- type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
138
- organizationId: organizationId,
139
- parameters: {
140
- signWith: signWith,
141
- payload: payload,
142
- encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
143
- hashFunction: "HASH_FUNCTION_NO_OP",
144
- },
145
- timestampMs: String(Date.now()), // millisecond timestamp
146
- });
147
-
148
- assertActivityCompleted(activity);
149
-
150
- const attestationObject = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.attestationObject);
151
- const decodedAttestationObj = decodeAttestationObject((base64UrlToBuffer(attestationObject) as unknown) as Uint8Array<ArrayBuffer>)
152
-
153
- const clientDataJson = assertNonNull(activity.intent.createOrganizationIntent?.rootAuthenticator.attestation.response.clientDataJson);
154
- const decodedClientDataJson = decodeClientDataJSON(clientDataJson);
155
-
156
- const signature = assertNonNull(activity.result.signRawPayloadResult);
157
-
158
- return {
159
- authenticatorData: toHex(decodedAttestationObj.get("authData")),
160
- clientDataJSON: JSON.stringify(decodedClientDataJson),
161
- challengeIndex: BigInt(23),
162
- typeIndex: BigInt(1),
163
- r: BigInt(signature.r),
164
- s: BigInt(signature.s)
165
- }
166
- }
167
-
168
- export const _stampAndSignMessageWithTurnkey = async (client: TurnkeyClient, organizationId: string, signWith: Address, payload: SignableMessage): Promise<WebAuthnSignature> => {
169
- let signedRequest;
170
- try {
171
- signedRequest = await client.stampSignRawPayload({
172
- type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
173
- organizationId: organizationId,
174
- parameters: {
175
- signWith: signWith,
176
- payload: `${payload}`,
177
- encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
178
- hashFunction: "HASH_FUNCTION_NO_OP",
179
- },
180
- timestampMs: String(Date.now()), // millisecond timestamp
181
- });
182
- }
183
- catch (e) {
184
- console.log("SDK error with signedRequest: ", e);
185
- }
186
- if (!signedRequest)
187
- throw new Error('Error: Failed to get signed request');
188
-
189
- const stampedHeaderValue = JSON.parse(signedRequest.stamp.stampHeaderValue);
190
- let response;
191
- try {
192
- const res = await fetch(signedRequest.url, {
193
- method: 'POST',
194
- headers: {
195
- 'Content-Type': 'application/json',
196
- [signedRequest.stamp.stampHeaderName]: signedRequest.stamp.stampHeaderValue
197
- },
198
- body: signedRequest.body
199
- });
200
- if (!res.ok) {
201
- const errorBody = await res.text();
202
- throw new Error(`SDK Turnkey API request failed: ${res.status} ${res.statusText} - ${errorBody}`);
203
- }
204
- const responseData = await res.json();
205
- response = responseData;
206
- }
207
- catch (e) {
208
- console.log("SDK error with response: ", e);
209
- }
210
-
211
- const authenticatorData = isoBase64URL.toBuffer(stampedHeaderValue.authenticatorData);
212
- const decodedClientDataJson = decodeClientDataJSON(stampedHeaderValue.clientDataJson);
213
- // `payload` here is the hex string (e.g., "0x...") that was sent to Turnkey
214
- // const originalPayloadBytes = Buffer.from(payload.startsWith('0x') ? payload.substring(2) : payload, 'hex');
215
- const originalPayloadBytes = getBytesFromPayload(payload as Hex);
216
- // Replace the challenge in decodedClientDataJson with the Base64URL of the *actual bytes*
217
- // isoBase64URL.fromBuffer from simplewebauthn/server removes padding, which is standard for WebAuthn.
218
- decodedClientDataJson.challenge = isoBase64URL.fromBuffer((originalPayloadBytes as unknown) as Uint8Array<ArrayBuffer>);
219
- const typeIndex = JSON.stringify(decodedClientDataJson).indexOf('"type":"webauthn.get"');
220
- const challengeIndex = JSON.stringify(decodedClientDataJson).indexOf('"challenge":');
221
- if (typeIndex === -1 || challengeIndex === -1) {
222
- throw new Error("Could not find type or challenge in clientDataJSON");
223
- }
224
- const signature = assertNonNull(response.activity.result.signRawPayloadResult);
225
-
226
- return {
227
- authenticatorData: toHex(authenticatorData),
228
- clientDataJSON: JSON.stringify(decodedClientDataJson),
229
- challengeIndex: BigInt(challengeIndex),
230
- typeIndex: BigInt(typeIndex),
231
- r: BigInt(_add0x(signature.r)),
232
- s: BigInt(_add0x(signature.s))
233
- };
234
- }
235
-
236
- export const _stampAndSignTypedDataWithTurnkey = async <
237
- const typedData extends TypedData | Record<string, unknown>,
238
- primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
239
- > (client: TurnkeyClient, organizationId: string, signWith: Address, typedData: TypedDataDefinition<typedData, primaryType>): Promise<WebAuthnSignature> => {
240
- const payload = hashTypedData(typedData);
241
- let signedRequest;
242
- try {
243
- signedRequest = await client.stampSignRawPayload({
244
- type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
245
- organizationId: organizationId,
246
- parameters: {
247
- signWith: signWith,
248
- payload: payload,
249
- encoding: "PAYLOAD_ENCODING_HEXADECIMAL",
250
- hashFunction: "HASH_FUNCTION_NO_OP",
251
- },
252
- timestampMs: String(Date.now()), // millisecond timestamp
253
- });
254
- }
255
- catch (e) {
256
- console.log("SDK error: stampAndSignTypedData");
257
- console.error(e);
258
- }
259
-
260
- if (!signedRequest)
261
- throw new Error('Error: Failed to get signed request');
262
- const stampedHeaderValue = JSON.parse(signedRequest.stamp.stampHeaderValue);
263
- let response;
264
- try {
265
- const res = await fetch(signedRequest.url, {
266
- method: 'POST',
267
- headers: {
268
- 'Content-Type': 'application/json',
269
- [signedRequest.stamp.stampHeaderName]: signedRequest.stamp.stampHeaderValue
270
- },
271
- body: signedRequest.body
272
- });
273
- if (!res.ok) {
274
- const errorBody = await res.text();
275
- throw new Error(`SDK Turnkey API request failed: ${res.status} ${res.statusText} - ${errorBody}`);
276
- }
277
- const responseData = await res.json();
278
- response = responseData;
279
- }
280
- catch (e) {
281
- console.log("SDK error with response: ", e);
282
- }
283
-
284
- const authenticatorData = isoBase64URL.toBuffer(stampedHeaderValue.authenticatorData);
285
- const decodedClientDataJson = decodeClientDataJSON(stampedHeaderValue.clientDataJson);
286
- const signature = assertNonNull(response.activity.result.signRawPayloadResult);
287
-
288
- return {
289
- authenticatorData: toHex(authenticatorData),
290
- clientDataJSON: JSON.stringify(decodedClientDataJson),
291
- challengeIndex: BigInt(23),
292
- typeIndex: BigInt(1),
293
- r: BigInt(_add0x(signature.r)),
294
- s: BigInt(_add0x(signature.s))
295
- };
296
- }