Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(PSDK-670): Support external wallet imports, wallet imports from CDP Python SDK #347

Merged
merged 14 commits into from
Dec 19, 2024
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

## Unreleased
- Add support for fetching address reputation
- Add `reputation` method to `Address` to fetch the reputation of the address.
- Add `reputation` method to `Address` to fetch the reputation of the address
- Add `networkId` to `WalletData` so that it is saved with the seed data and surfaced via the export function
- Add ability to import external wallets into CDP via a BIP-39 mnemonic phrase, as a 1-of-1 wallet
- Add ability to import WalletData files exported by the Python CDP SDK
- Deprecate `Wallet.loadSeed()` method in favor of `Wallet.loadSeedFromFile()`
- Deprecate `Wallet.saveSeed()` method in favor of `Wallet.saveSeedToFile()`
Comment on lines +9 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll move these around as we prep the release, but we'll have a separate heading for "Address" and "Deprecated"


## [0.12.0] - Skipped

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,13 @@ For convenience during testing, we provide a `saveSeed` method that stores the w

```typescript
const seedFilePath = "";
wallet.saveSeed(seedFilePath);
wallet.saveSeedToFile(seedFilePath);
```

To encrypt the saved data, set encrypt to true. Note that your CDP API key also serves as the encryption key for the data persisted locally. To re-instantiate wallets with encrypted data, ensure that your SDK is configured with the same API key when invoking `saveSeed` and `loadSeed`.

```typescript
wallet.saveSeed(seedFilePath, true);
wallet.saveSeedToFile(seedFilePath, true);
```

The below code demonstrates how to re-instantiate a Wallet from the data export.
Expand All @@ -221,7 +221,7 @@ To import Wallets that were persisted to your local file system using `saveSeed`

```typescript
const userWallet = await Wallet.fetch(wallet.getId());
await userWallet.loadSeed(seedFilePath);
await userWallet.loadSeedFromFile(seedFilePath);
```


Expand Down
2 changes: 1 addition & 1 deletion quickstart-template/bridge-usdc.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async function getTransactionReceipt(txHash) {
async function fetchWalletAndLoadSeed(walletId, seedFilePath) {
try {
const wallet = await Wallet.fetch(walletId);
await wallet.loadSeed(seedFilePath);
await wallet.loadSeedFromFile(seedFilePath);

console.log(`Successfully loaded funded wallet: `, wallet.getId());
return wallet;
Expand Down
2 changes: 1 addition & 1 deletion quickstart-template/discord_tutorial/webhook-transfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const webhookNotificationUri = process.env.WEBHOOK_NOTIFICATION_URL;
// Create Wallet
else {
myWallet = await Wallet.create();
const saveSeed = myWallet.saveSeed(seedPath);
const saveSeed = myWallet.saveSeedToFile(seedPath);
console.log("✅ Seed saved: ", saveSeed);
}

Expand Down
2 changes: 1 addition & 1 deletion quickstart-template/register-basename.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ async function registerBaseName(wallet, registerArgs) {
async function fetchWalletAndLoadSeed(walletId, seedFilePath) {
try {
const wallet = await Wallet.fetch(walletId);
await wallet.loadSeed(seedFilePath);
await wallet.loadSeedFromFile(seedFilePath);

console.log(`Successfully loaded funded wallet: `, wallet);
return wallet;
Expand Down
98 changes: 86 additions & 12 deletions src/coinbase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export type WalletAPIClient = {
* List wallets belonging to the user.
*
* @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
* @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param page - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param options - Override http request option.
* @throws {APIError} If the request fails.
* @throws {RequiredError} If the required parameter is not provided.
Expand Down Expand Up @@ -358,7 +358,7 @@ export type AddressAPIClient = {
* @param walletId - The ID of the wallet the address belongs to.
* @param addressId - The onchain address of the address to sign the payload with.
* @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
* @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param page - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param options - Axios request options.
* @throws {APIError} If the request fails.
*/
Expand All @@ -380,7 +380,7 @@ export type ExternalAddressAPIClient = {
*
* @param networkId - The ID of the blockchain network
* @param addressId - The ID of the address to fetch the balance for
* @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param page - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param options - Override http request option.
* @throws {APIError} If the request fails.
*/
Expand Down Expand Up @@ -812,14 +812,87 @@ export enum FundOperationStatus {
}

/**
* The Wallet Data type definition.
* The data required to recreate a Wallet.
* Interface representing wallet data, with support for both camelCase and snake_case
* property names for compatibility with older versions of the Python SDK.
*/
export type WalletData = {
walletId: string;
export interface WalletData {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious on reasoning to convert this from a type to an interface?

/**
* The CDP wallet ID in either camelCase or snake_case format, but not both.
*/
walletId?: string;
wallet_id?: string;
Comment on lines +819 to +823
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option to try to enforce mutual exclusivity is to use never:

type WalletData = {
    walletId: string;
    wallet_id?: never;
} | {
    walletId?: never;
    wallet_id: string;
}

const walletData: WalletData = {
    walletId: '123',
    wallet_id: '123'
}

Now TypeScript will error with a message:

Type '{ walletId: string; wallet_id: string; }' is not assignable to type 'WalletData'.
  Types of property 'wallet_id' are incompatible.
    Type 'string' is not assignable to type 'undefined'

Admittedly, the error message could be clearer, but it's difficult to customize the message without reaching for a type validation library like Zod, which might be overkill here.


/**
* The wallet seed
*/
seed: string;

/**
* The network ID in either camelCase or snake_case format, but not both.
*/
networkId?: string;
};
network_id?: string;
}

/**
* Type guard to check if data matches the appropriate WalletData format.
* WalletData must have:
* - exactly one of (walletId or wallet_id)
* - at most one of (networkId or network_id)
* - a seed
*
* @param data - The data to check
* @returns True if data matches the appropriate WalletData format
*/
export function isWalletData(data: unknown): data is WalletData {
if (typeof data !== "object" || data === null) {
return false;
}

const { walletId, wallet_id, networkId, network_id, seed } = data as WalletData;

// Check that exactly one of walletId or wallet_id is present (but not both)
const hasWalletId = typeof walletId === "string";
const hasWalletSnakeId = typeof wallet_id === "string";
if (!(hasWalletId !== hasWalletSnakeId)) {
return false;
}

// Check that at most one of networkId or network_id is present (but not both)
const hasNetworkId = typeof networkId === "string";
const hasNetworkSnakeId = typeof network_id === "string";
if (hasNetworkId && hasNetworkSnakeId) {
return false;
}

// Check that seed is present and is a string
return typeof seed === "string";
}

/**
* Interface representing a BIP-39 mnemonic seed phrase.
*/
export interface MnemonicSeedPhrase {
/**
* The BIP-39 mnemonic seed phrase (12, 15, 18, 21, or 24 words)
*/
mnemonicPhrase: string;
}

/**
* Type guard to check if data matches the MnemonicSeedPhrase format.
*
* @param data - The data to check
* @returns True if data matches the MnemonicSeedPhrase format
*/
export function isMnemonicSeedPhrase(data: unknown): data is MnemonicSeedPhrase {
if (typeof data !== "object" || data === null) {
return false;
}

const { mnemonicPhrase } = data as MnemonicSeedPhrase;
return typeof mnemonicPhrase === "string";
}

/**
* The Seed Data type definition.
Expand Down Expand Up @@ -854,6 +927,7 @@ export enum ServerSignerStatus {
* Options for creating a Wallet.
*/
export type WalletCreateOptions = {
seed?: string;
networkId?: string;
timeoutSeconds?: number;
intervalSeconds?: number;
Expand Down Expand Up @@ -1147,7 +1221,7 @@ export interface WebhookApiClient {
*
* @summary List webhooks
* @param {number} [limit] - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
* @param {string} [page] - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param {string} [page] - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param {*} [options] - Override http request option.
* @throws {RequiredError}
*/
Expand Down Expand Up @@ -1182,7 +1256,7 @@ export interface BalanceHistoryApiClient {
* @param addressId - The ID of the address to fetch the historical balance for.
* @param assetId - The symbol of the asset to fetch the historical balance for.
* @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
* @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param page - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param options - Override http request option.
* @throws {RequiredError}
*/
Expand All @@ -1204,7 +1278,7 @@ export interface TransactionHistoryApiClient {
* @param networkId - The ID of the blockchain network
* @param addressId - The ID of the address to fetch transactions for.
* @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
* @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param page - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param options - Override http request option.
* @throws {RequiredError}
*/
Expand Down Expand Up @@ -1446,7 +1520,7 @@ export interface FundOperationApiClient {
* @param walletId - The ID of the wallet the address belongs to.
* @param addressId - The ID of the address to list fund operations for.
* @param limit - A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
* @param page - A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param page - A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
* @param options - Axios request options
* @throws {APIError} If the request fails
*/
Expand Down
Loading
Loading