Skip to content

Commit

Permalink
Add support for ERC1155 contract deployments (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohan-agarwal-coinbase authored Sep 25, 2024
1 parent af721ee commit 923eaf8
Show file tree
Hide file tree
Showing 9 changed files with 444 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added
- Add `deployNFT` method to `WalletAddress` and `Wallet` to deploy an ERC721, updated `SmartContract` class to support deployment and fetching contract details
- Add `deployMultiToken` method to `WalletAddress` and `Wallet` to deploy an ERC1155, updated `SmartContract` class to support deployment and fetching contract details

## [0.6.1] - 2024-09-23

Expand Down
18 changes: 16 additions & 2 deletions src/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,19 @@ export interface ModelError {
*/
'correlation_id'?: string;
}
/**
* Options for multi-token contract creation
* @export
* @interface MultiTokenContractOptions
*/
export interface MultiTokenContractOptions {
/**
* The URI for all token metadata
* @type {string}
* @memberof MultiTokenContractOptions
*/
'uri': string;
}
/**
* Options for NFT contract creation
* @export
Expand Down Expand Up @@ -2151,7 +2164,7 @@ export interface SmartContractList {
* Options for smart contract creation
* @export
*/
export type SmartContractOptions = NFTContractOptions | TokenContractOptions;
export type SmartContractOptions = MultiTokenContractOptions | NFTContractOptions | TokenContractOptions;

/**
* The type of the smart contract
Expand All @@ -2161,7 +2174,8 @@ export type SmartContractOptions = NFTContractOptions | TokenContractOptions;

export const SmartContractType = {
Erc20: 'erc20',
Erc721: 'erc721'
Erc721: 'erc721',
Erc1155: 'erc1155'
} as const;

export type SmartContractType = typeof SmartContractType[keyof typeof SmartContractType];
Expand Down
49 changes: 49 additions & 0 deletions src/coinbase/address/wallet_address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
StakeOptionsMode,
CreateERC20Options,
CreateERC721Options,
CreateERC1155Options,
} from "../types";
import { delay } from "../utils";
import { Wallet as WalletClass } from "../wallet";
Expand Down Expand Up @@ -379,6 +380,31 @@ export class WalletAddress extends Address {
return smartContract;
}

/**
* Deploys an ERC1155 multi-token contract.
*
* @param options - The options for creating the ERC1155 token.
* @param options.uri - The URI for all token metadata.
* @returns A Promise that resolves to the deployed SmartContract object.
* @throws {APIError} If the API request to create a smart contract fails.
*/
public async deployMultiToken(options: CreateERC1155Options): Promise<SmartContract> {
if (!Coinbase.useServerSigner && !this.key) {
throw new Error("Cannot deploy ERC1155 without private key loaded");
}

const smartContract = await this.createERC1155(options);

if (Coinbase.useServerSigner) {
return smartContract;
}

await smartContract.sign(this.getSigner());
await smartContract.broadcast();

return smartContract;
}

/**
* Creates an ERC20 token contract.
*
Expand Down Expand Up @@ -432,6 +458,29 @@ export class WalletAddress extends Address {
return SmartContract.fromModel(resp?.data);
}

/**
* Creates an ERC1155 multi-token contract.
*
* @private
* @param {CreateERC1155Options} options - The options for creating the ERC1155 token.
* @param {string} options.uri - The URI for all token metadata.
* @returns {Promise<SmartContract>} A Promise that resolves to the created SmartContract.
* @throws {APIError} If the API request to create a smart contract fails.
*/
private async createERC1155(options: CreateERC1155Options): Promise<SmartContract> {
const resp = await Coinbase.apiClients.smartContract!.createSmartContract(
this.getWalletId(),
this.getId(),
{
type: SmartContractType.Erc1155,
options: {
uri: options.uri,
},
},
);
return SmartContract.fromModel(resp?.data);
}

/**
* Creates a contract invocation with the given data.
*
Expand Down
24 changes: 23 additions & 1 deletion src/coinbase/smart_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import {
SmartContractType as SmartContractTypeModel,
SmartContractOptions as SmartContractOptionsModel,
TokenContractOptions as TokenContractOptionsModel,
NFTContractOptions as NFTContractOptionsModel,
} from "../client/api";
import { Transaction } from "./transaction";
import {
SmartContractOptions,
SmartContractType,
NFTContractOptions,
TokenContractOptions,
MultiTokenContractOptions,
TransactionStatus,
} from "./types";
import { Coinbase } from "./coinbase";
Expand Down Expand Up @@ -155,6 +157,8 @@ export class SmartContract {
return SmartContractType.ERC20;
case SmartContractTypeModel.Erc721:
return SmartContractType.ERC721;
case SmartContractTypeModel.Erc1155:
return SmartContractType.ERC1155;
default:
throw new Error(`Unknown smart contract type: ${this.model.type}`);
}
Expand All @@ -172,12 +176,16 @@ export class SmartContract {
symbol: this.model.options.symbol,
totalSupply: this.model.options.total_supply,
} as TokenContractOptions;
} else {
} else if (this.isERC721(this.getType(), this.model.options)) {
return {
name: this.model.options.name,
symbol: this.model.options.symbol,
baseURI: this.model.options.base_uri,
} as NFTContractOptions;
} else {
return {
uri: this.model.options.uri,
} as MultiTokenContractOptions;
}
}

Expand Down Expand Up @@ -304,4 +312,18 @@ export class SmartContract {
): options is TokenContractOptionsModel {
return type === SmartContractType.ERC20;
}

/**
* Type guard for checking if the smart contract is an ERC721.
*
* @param type - The type of the smart contract.
* @param options - The options of the smart contract.
* @returns True if the smart contract is an ERC721, false otherwise.
*/
private isERC721(
type: SmartContractType,
options: SmartContractOptionsModel,
): options is NFTContractOptionsModel {
return type === SmartContractType.ERC721;
}
}
20 changes: 19 additions & 1 deletion src/coinbase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,7 @@ export enum StakeOptionsMode {
export enum SmartContractType {
ERC20 = "erc20",
ERC721 = "erc721",
ERC1155 = "erc1155",
}

/**
Expand All @@ -932,10 +933,20 @@ export type TokenContractOptions = {
totalSupply: string;
};

/**
* Multi-Token Contract Options
*/
export type MultiTokenContractOptions = {
uri: string;
};

/**
* Smart Contract Options
*/
export type SmartContractOptions = NFTContractOptions | TokenContractOptions;
export type SmartContractOptions =
| NFTContractOptions
| TokenContractOptions
| MultiTokenContractOptions;

/**
* Options for creating a Transfer.
Expand Down Expand Up @@ -986,6 +997,13 @@ export type CreateERC721Options = {
baseURI: string;
};

/**
* Options for creating a ERC1155.
*/
export type CreateERC1155Options = {
uri: string;
};

/**
* Options for listing historical balances of an address.
*/
Expand Down
17 changes: 16 additions & 1 deletion src/coinbase/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
WalletData,
CreateERC20Options,
CreateERC721Options,
CreateERC1155Options,
} from "./types";
import { convertStringToHex, delay, formatDate, getWeekBackDate } from "./utils";
import { StakingOperation } from "./staking_operation";
Expand Down Expand Up @@ -792,12 +793,26 @@ export class Wallet {
* @param options.symbol - The symbol of the ERC721 token.
* @param options.baseURI - The base URI of the ERC721 token.
* @returns A Promise that resolves to the deployed SmartContract object.
* @throws {APIError} If the private key is not loaded when not using server signer.
* @throws {Error} If the private key is not loaded when not using server signer.
*/
public async deployNFT(options: CreateERC721Options): Promise<SmartContract> {
return (await this.getDefaultAddress()).deployNFT(options);
}

/**
* Deploys an ERC1155 token contract.
*
* @param options - The options for creating the ERC1155 token.
* @param options.name - The name of the ERC1155 token.
* @param options.symbol - The symbol of the ERC1155 token.
* @param options.baseURI - The base URI of the ERC1155 token.
* @returns A Promise that resolves to the deployed SmartContract object.
* @throws {Error} If the private key is not loaded when not using server signer.
*/
public async deployMultiToken(options: CreateERC1155Options): Promise<SmartContract> {
return (await this.getDefaultAddress()).deployMultiToken(options);
}

/**
* Returns a String representation of the Wallet.
*
Expand Down
22 changes: 20 additions & 2 deletions src/tests/smart_contract_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
ERC721_NAME,
ERC721_SYMBOL,
ERC721_BASE_URI,
VALID_SMART_CONTRACT_ERC1155_MODEL,
ERC1155_URI,
} from "./utils";
import { SmartContract } from "../coinbase/smart_contract";
import { ContractEvent } from "../coinbase/contract_event";
Expand All @@ -30,6 +32,8 @@ describe("SmartContract", () => {
let erc721Model: SmartContractModel = VALID_SMART_CONTRACT_ERC721_MODEL;
let erc20SmartContract: SmartContract = SmartContract.fromModel(erc20Model);
let erc721SmartContract: SmartContract = SmartContract.fromModel(erc721Model);
let erc1155Model: SmartContractModel = VALID_SMART_CONTRACT_ERC1155_MODEL;
let erc1155SmartContract: SmartContract = SmartContract.fromModel(erc1155Model);
afterEach(() => {
jest.clearAllMocks();
});
Expand Down Expand Up @@ -79,13 +83,21 @@ describe("SmartContract", () => {
});

describe("#getType", () => {
it("returns the smart contract type", () => {
it("returns the smart contract type for ERC20", () => {
expect(erc20SmartContract.getType()).toEqual(VALID_SMART_CONTRACT_ERC20_MODEL.type);
});

it("returns the smart contract type for ERC721", () => {
expect(erc721SmartContract.getType()).toEqual(VALID_SMART_CONTRACT_ERC721_MODEL.type);
});

it("returns the smart contract type for ERC1155", () => {
expect(erc1155SmartContract.getType()).toEqual(VALID_SMART_CONTRACT_ERC1155_MODEL.type);
});
});

describe("#getOptions", () => {
it("returns the smart contract options", () => {
it("returns the smart contract options for ERC20", () => {
expect(erc20SmartContract.getOptions()).toEqual({
name: ERC20_NAME,
symbol: ERC20_SYMBOL,
Expand All @@ -100,6 +112,12 @@ describe("SmartContract", () => {
baseURI: ERC721_BASE_URI,
});
});

it("returns the smart contract options for ERC1155", () => {
expect(erc1155SmartContract.getOptions()).toEqual({
uri: ERC1155_URI,
});
});
});

describe("#getAbi", () => {
Expand Down
22 changes: 22 additions & 0 deletions src/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ValidatorStatus,
NFTContractOptions as NFTContractOptionsModel,
TokenContractOptions as TokenContractOptionsModel,
MultiTokenContractOptions as MultiTokenContractOptionsModel,
} from "../client";
import { BASE_PATH } from "../client/base";
import { Coinbase } from "../coinbase/coinbase";
Expand Down Expand Up @@ -331,6 +332,27 @@ export const VALID_SMART_CONTRACT_ERC721_MODEL: SmartContractModel = {
},
};

export const ERC1155_URI = "https://example.com/{id}.json";
export const VALID_SMART_CONTRACT_ERC1155_MODEL: SmartContractModel = {
smart_contract_id: "test-smart-contract-1",
network_id: Coinbase.networks.BaseSepolia,
wallet_id: walletId,
contract_address: "0xcontract-address",
deployer_address: "0xdeployer-address",
type: SmartContractType.Erc1155,
options: {
uri: ERC1155_URI,
} as MultiTokenContractOptionsModel,
abi: JSON.stringify("some-abi"),
transaction: {
network_id: Coinbase.networks.BaseSepolia,
from_address_id: "0xdeadbeef",
unsigned_payload:
"7b2274797065223a22307832222c22636861696e4964223a2230783134613334222c226e6f6e6365223a22307830222c22746f223a22307861383261623835303466646562326461646161336234663037356539363762626533353036356239222c22676173223a22307865623338222c226761735072696365223a6e756c6c2c226d61785072696f72697479466565506572476173223a2230786634323430222c226d6178466565506572476173223a2230786634333638222c2276616c7565223a22307830222c22696e707574223a223078366136323738343230303030303030303030303030303030303030303030303034373564343164653761383132393862613236333138343939363830306362636161643733633062222c226163636573734c697374223a5b5d2c2276223a22307830222c2272223a22307830222c2273223a22307830222c2279506172697479223a22307830222c2268617368223a22307865333131636632303063643237326639313566656433323165663065376431653965353362393761346166623737336638653935646431343630653665326163227d",
status: TransactionStatusEnum.Pending,
},
};

/**
* mockStakingOperation returns a mock StakingOperation object with the provided status.
*
Expand Down
Loading

0 comments on commit 923eaf8

Please sign in to comment.