Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ import { numberToHex } from 'viem';
type AddSubAccountProps = {
sdk: ReturnType<typeof createCoinbaseWalletSDK>;
onAddSubAccount: (address: string) => void;
signerFn: typeof getCryptoKeyAccount;
};

export function AddSubAccount({ sdk, onAddSubAccount }: AddSubAccountProps) {
export function AddSubAccount({ sdk, onAddSubAccount, signerFn }: AddSubAccountProps) {
const [subAccount, setSubAccount] = useState<string>();

const handleAddSubAccount = useCallback(async () => {
if (!sdk) {
return;
}

const { account } = await signerFn();

if (!account) {
throw new Error('Could not get owner account');
}

const provider = sdk.getProvider();
const { account } = await getCryptoKeyAccount();
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: numberToHex(84532) }],
Expand All @@ -29,12 +36,20 @@ export function AddSubAccount({ sdk, onAddSubAccount }: AddSubAccountProps) {
version: '1',
account: {
type: 'create',
keys: [
{
type: 'webauthn-p256',
key: account.publicKey,
},
],
keys:
(account.type as string) === 'webAuthn'
? [
{
type: 'webauthn-p256',
key: account.publicKey,
},
]
: [
{
type: 'address',
key: account.address,
},
],
},
},
],
Expand All @@ -43,7 +58,7 @@ export function AddSubAccount({ sdk, onAddSubAccount }: AddSubAccountProps) {
console.info('response', response);
setSubAccount(response.address);
onAddSubAccount(response.address);
}, [sdk, onAddSubAccount]);
}, [sdk, onAddSubAccount, signerFn]);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Box, Button } from '@chakra-ui/react';
import { createCoinbaseWalletSDK } from '@coinbase/wallet-sdk';
import { useCallback, useState } from 'react';
import { numberToHex } from 'viem';
import { baseSepolia } from 'viem/chains';

export function SendCalls({
Expand All @@ -22,9 +23,15 @@ export function SendCalls({
method: 'wallet_sendCalls',
params: [
{
chainId: baseSepolia.id,
chainId: numberToHex(baseSepolia.id),
from: subAccountAddress,
calls: [],
calls: [
{
to: '0x000000000000000000000000000000000000dead',
data: '0x',
value: '0x0',
},
],
version: '1',
capabilities: {
paymasterService: {
Expand Down
70 changes: 61 additions & 9 deletions examples/testapp/src/pages/add-sub-account/index.page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Container, VStack } from '@chakra-ui/react';
import {
Container,
FormControl,
FormLabel,
Radio,
RadioGroup,
Stack,
VStack,
} from '@chakra-ui/react';
import { createCoinbaseWalletSDK, getCryptoKeyAccount } from '@coinbase/wallet-sdk';
import { useEffect, useState } from 'react';

import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { AddOwner } from './components/AddOwner';
import { AddSubAccount } from './components/AddSubAccount';
import { Connect } from './components/Connect';
Expand All @@ -11,9 +19,44 @@ import { PersonalSign } from './components/PersonalSign';
import { SendCalls } from './components/SendCalls';
import { SpendPermissions } from './components/SpendPermissions';

type SignerType = 'cryptokey' | 'secp256k1';

export default function SubAccounts() {
const [sdk, setSDK] = useState<ReturnType<typeof createCoinbaseWalletSDK>>();
const [subAccountAddress, setSubAccountAddress] = useState<string>();
const [signerType, setSignerType] = useState<SignerType>('cryptokey');
const [getSubAccountSigner, setGetSubAccountSigner] = useState<typeof getCryptoKeyAccount>(
() => getCryptoKeyAccount
);

useEffect(() => {
const stored = localStorage.getItem('signer-type');
if (stored !== null) {
setSignerType(stored as SignerType);
}
}, []);

useEffect(() => {
localStorage.setItem('signer-type', signerType);
}, [signerType]);

useEffect(() => {
const getSigner =
signerType === 'cryptokey'
? getCryptoKeyAccount
: async () => {
let privateKey = localStorage.getItem('cbwsdk.demo.add-sub-account.pk') as `0x${string}` | null;
if (!privateKey) {
privateKey = generatePrivateKey();
localStorage.setItem('cbwsdk.demo.add-sub-account.pk', privateKey);
}
return {
account: privateKeyToAccount(privateKey),
};
};

setGetSubAccountSigner(() => getSigner);
}, [signerType]);

useEffect(() => {
const sdk = createCoinbaseWalletSDK({
Expand All @@ -22,26 +65,35 @@ export default function SubAccounts() {
keysUrl: 'http://localhost:3005/connect',
options: 'smartWalletOnly',
},
toSubAccountSigner: getCryptoKeyAccount,
toSubAccountSigner: getSubAccountSigner,
});

if (!sdk) {
return;
}

setSDK(sdk);
const provider = sdk.getProvider();

provider.on('accountsChanged', (accounts) => {
console.info('accountsChanged', accounts);
});
}, []);
}, [getSubAccountSigner]);

return (
<Container mb={16}>
<VStack w="full" spacing={4}>
<FormControl>
<FormLabel>Select Signer Type</FormLabel>
<RadioGroup value={signerType} onChange={(value: SignerType) => setSignerType(value)}>
<Stack direction="row">
<Radio value="cryptokey">CryptoKey</Radio>
<Radio value="secp256k1">secp256k1</Radio>
</Stack>
</RadioGroup>
</FormControl>
<Connect sdk={sdk} />
<AddSubAccount sdk={sdk} onAddSubAccount={setSubAccountAddress} />
<AddSubAccount
sdk={sdk}
onAddSubAccount={setSubAccountAddress}
signerFn={getSubAccountSigner}
/>
<PersonalSign sdk={sdk} subAccountAddress={subAccountAddress} />
<SendCalls sdk={sdk} subAccountAddress={subAccountAddress} />
<GrantSpendPermission sdk={sdk} subAccountAddress={subAccountAddress} />
Expand Down
30 changes: 30 additions & 0 deletions packages/wallet-sdk/src/core/rpc/wallet_prepareCalls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Hex } from 'viem';

export type PrepareCallsParams = [
{
from: Hex;
chainId: Hex;
calls: {
to: Hex;
data: Hex;
value: Hex;
}[];
capabilities: Record<string, any>;
},
];

export type PrepareCallsReturnValue = {
type: string;
chainId: Hex;
signatureRequest: {
hash: Hex;
};
capabilities: Record<string, any>;
userOp: any;
};

export type PrepareCallsSchema = {
Method: 'wallet_prepareCalls';
Parameters: PrepareCallsParams;
ReturnType: PrepareCallsReturnValue;
};
51 changes: 51 additions & 0 deletions packages/wallet-sdk/src/core/rpc/wallet_sendPreparedCalls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Hex } from 'viem';

export type WebauthnSignatureType = {
type: 'webauthn';
data: {
/**
* The signature is the stringified JSON of the webauthn signature
* @example
* ```json
* {
* "id": "string",
* "rawId": "string",
* "response": {
* "authenticatorData": "string",
* "clientDataJSON": "string",
* "signature": "string"
* },
* "type": "string"
* }
* ```
*/
signature: string;
publicKey: Hex;
};
};

export type Secp256k1SignatureType = {
type: 'secp256k1';
data: {
address: Hex;
signature: Hex;
};
};

export type SendPreparedCallsParams = [
{
version: string;
type: string;
data: any;
chainId: Hex;
signature: WebauthnSignatureType | Secp256k1SignatureType;
},
];

export type SendPreparedCallsReturnValue = string[];

export type SendPreparedCallsSchema = {
Method: 'wallet_sendPreparedCalls';
Parameters: SendPreparedCallsParams;
ReturnType: SendPreparedCallsReturnValue;
};
2 changes: 1 addition & 1 deletion packages/wallet-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import { CoinbaseWalletSDK } from './CoinbaseWalletSDK.js';
export default CoinbaseWalletSDK;

export type { AppMetadata, Preference, ProviderInterface } from ':core/provider/interface.js';
export type { CoinbaseWalletProvider } from './CoinbaseWalletProvider.js';
export { CoinbaseWalletSDK } from './CoinbaseWalletSDK.js';
export { createCoinbaseWalletSDK } from './createCoinbaseWalletSDK.js';
export { getCryptoKeyAccount, removeCryptoKey } from './kms/crypto-key/index.js';
export type { AppMetadata, Preference, ProviderInterface } from ':core/provider/interface.js';
11 changes: 6 additions & 5 deletions packages/wallet-sdk/src/sign/scw/SCWSigner.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { Hex, numberToHex } from 'viem';

import { Signer } from '../interface.js';
import { SCWKeyManager } from './SCWKeyManager.js';
import { addSenderToRequest, assertParamsChainId, getSenderFromRequest } from './utils.js';
import { createSubAccountSigner } from './utils/createSubAccountSigner.js';
import { Communicator } from ':core/communicator/Communicator.js';
import { standardErrors } from ':core/error/errors.js';
import { RPCRequestMessage, RPCResponseMessage } from ':core/message/RPCMessage.js';
Expand All @@ -12,7 +8,7 @@ import { AppMetadata, ProviderEventCallback, RequestArguments } from ':core/prov
import { WalletConnectResponse } from ':core/rpc/wallet_connect.js';
import { Address } from ':core/type/index.js';
import { ensureIntNumber, hexStringFromNumber } from ':core/type/util.js';
import { createClients, SDKChain } from ':store/chain-clients/utils.js';
import { SDKChain, createClients } from ':store/chain-clients/utils.js';
import { config } from ':store/config.js';
import { store } from ':store/store.js';
import { assertPresence } from ':util/assertPresence.js';
Expand All @@ -24,6 +20,10 @@ import {
importKeyFromHexString,
} from ':util/cipher.js';
import { fetchRPCRequest } from ':util/provider.js';
import { Signer } from '../interface.js';
import { SCWKeyManager } from './SCWKeyManager.js';
import { addSenderToRequest, assertParamsChainId, getSenderFromRequest } from './utils.js';
import { createSubAccountSigner } from './utils/createSubAccountSigner.js';

type ConstructorOptions = {
metadata: AppMetadata;
Expand Down Expand Up @@ -382,6 +382,7 @@ export class SCWSigner implements Signer {
const signer = await createSubAccountSigner({
chainId: this.chain.id,
});

return signer.request(request);
}
}
Loading