Skip to content

Commit

Permalink
Feature: Add support nft deposits and withdrawals (#259)
Browse files Browse the repository at this point in the history
* add erc721 support for deposits and withdraw

* update changelog

* add encoding and decoding tests

* add use of openzeppelin abis

* fix comments

* update and simply deposit function

* update yarn.lock and remove unused error class

* update erc20 abi and deposit function

* add abstactions to class
  • Loading branch information
josemarinas authored Jul 27, 2023
1 parent f29d8de commit 1b4f360
Show file tree
Hide file tree
Showing 27 changed files with 421 additions and 202 deletions.
1 change: 1 addition & 0 deletions modules/client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ TEMPLATE:
## [UPCOMING]
### Added
- Added `initializeFrom` encoders and decoders
- Support for ERC721 deposits and withdrawals
### Fixes
- Fix status calculation for token voting proposals
- Make the `network` parameter required on `getPluginInstallItem`
Expand Down
69 changes: 69 additions & 0 deletions modules/client/examples/01-client/04-deposit-erc721.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* MARKDOWN
---
title: Deposit ERC-721
---
### Deposit ERC-721 Tokens to a DAO
Deposits ERC-721 tokens to a DAO.
- Similar to the ERC20 deposit flow
- The `tokenAddress` field is required. This is the contract address of the ERC-721 token.
- TokenId is required. This is the token ID of the ERC-721 token.
- Calls the safeTransferFrom function of the ERC-721 token contract.
*/

import {
Client,
DaoDepositSteps,
DepositParams,
SetAllowanceSteps,
} from "@aragon/sdk-client";
import { GasFeeEstimation, TokenType } from "@aragon/sdk-client-common";
import { context } from "../index";

// Instantiate the general purpose client from the Aragon OSx SDK context.
const client: Client = new Client(context);

const depositParams: DepositParams = {
daoAddressOrEns: "0x1234567890123456789012345678901234567890", // my-dao.dao.eth
tokenAddress: "0x1234567890123456789012345678901234567890", // token contract adddress
type: TokenType.ERC721, // "erc721" for ERC721 token
tokenId: BigInt(1), // token ID of the ERC-721 token
};

// Estimate how much gas the transaction will cost.
const estimatedGas: GasFeeEstimation = await client.estimation.deposit(
depositParams,
);
console.log({ avg: estimatedGas.average, max: estimatedGas.max });

// Deposit the ERC721 tokens.
const steps = client.methods.deposit(depositParams);
for await (const step of steps) {
try {
switch (step.key) {
case DaoDepositSteps.DEPOSITING:
console.log({ depositingTxHash: step.txHash });
break;
case DaoDepositSteps.DONE:
console.log({ tokenId: step.tokenId });
break;
}
} catch (err) {
console.error(err);
}
}

/* MARKDOWN
Returns:
```tsx
{
depositingTxHash: "0xb1c14a49...3e8620b0f5832d61c"
}
{
tokenId: 1n
}
```
*/

27 changes: 11 additions & 16 deletions modules/client/examples/05-encoders-decoders/06-withdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,33 +119,28 @@ Returns:
{ erc20DecodedParams:
{
type: "native",
type: "erc20",
recipientAddressOrEns: "0x1234567890123456789012345678901234567890",
amount: 10n,
tokenAddress: "0x1234567890123456789012345678901234567890",
}
}
:::info
Work in progress.
:::
*/

/* MARKDOWN
### NFT (ERC-721) Tokens
#### Encoding
:::info
Work in progress.
:::
*/

params = {
type: TokenType.ERC721, // TODO!!
type: TokenType.ERC721,
tokenAddress: "0x1234567890123456789012345678901234567890", // ERFC721's token contract address
amount: BigInt(10), // TODO!!
tokenId: BigInt(10),
recipientAddressOrEns: "0x1234567890123456789012345678901234567890", // the address to transfer the funds to
daoAddressOrEns: "0x1234567890123456789012345678901234567890", // the address of the DAO
};

const erc721WithdrawAction: DaoAction = await client.encoding.withdrawAction(
Expand All @@ -156,9 +151,6 @@ console.log({ erc721WithdrawAction });
/* MARKDOWN
#### Decoding
:::info
Work in progress.
:::
*/

const erc721DecodedParams = client.decoding.withdrawAction(
Expand All @@ -170,8 +162,11 @@ console.log({ erc721DecodedParams });

/* MARKDOWN
Returns:
:::info
Work in progress.
:::
{
type: TokenType.ERC721;
tokenAddress: "0x1234567890123456789012345678901234567890";
tokenId: 10n;
daoAddressOrEns: "0x1234567890123456789012345678901234567890";
recipientAddressOrEns: "0x1234567890123456789012345678901234567890";
}
*/
1 change: 1 addition & 0 deletions modules/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@ethersproject/contracts": "^5.5.0",
"@ethersproject/providers": "^5.5.0",
"@ethersproject/wallet": "^5.6.0",
"@openzeppelin/contracts": "^4.9.2",
"graphql": "^16.5.0",
"graphql-request": "^4.3.0"
},
Expand Down
82 changes: 0 additions & 82 deletions modules/client/src/internal/abi/erc20.ts

This file was deleted.

43 changes: 28 additions & 15 deletions modules/client/src/internal/client/decoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
IpfsError,
resolveIpfsCid,
} from "@aragon/sdk-common";
import { erc20ContractAbi } from "../abi/erc20";
import { abi as ERC20_ABI } from "@openzeppelin/contracts/build/contracts/ERC20.json";
import { abi as ERC721_ABI } from "@openzeppelin/contracts/build/contracts/ERC721.json";
import { Contract } from "@ethersproject/contracts";
import { AddressZero } from "@ethersproject/constants";
import { toUtf8String } from "@ethersproject/strings";
Expand Down Expand Up @@ -139,21 +140,33 @@ export class ClientDecoding extends ClientCore implements IClientDecoding {
}

// ERC20 and other
const abiObjects = [{
tokenStandard: TokenType.ERC20,
abi: erc20ContractAbi,
}];
const abiObjects = [
{
tokenStandard: TokenType.ERC20,
abi: ERC20_ABI,
function: "transfer",
},
{
tokenStandard: TokenType.ERC721,
abi: ERC721_ABI,
function: "safeTransferFrom(address,address,uint256)",
},
];
for (const abiObject of abiObjects) {
const hexBytes = bytesToHex(data);
const iface = new Contract(AddressZero, abiObject.abi).interface;
const expectedFunction = iface.getFunction("transfer");
const result = iface.decodeFunctionData(expectedFunction, hexBytes);
return withdrawParamsFromContract(
to,
value,
result,
abiObject.tokenStandard,
);
try {
const hexBytes = bytesToHex(data);
const iface = new Contract(AddressZero, abiObject.abi).interface;
const expectedFunction = iface.getFunction(abiObject.function);
const result = iface.decodeFunctionData(expectedFunction, hexBytes);
return withdrawParamsFromContract(
to,
value,
result,
abiObject.tokenStandard,
);
} catch (e) {
continue;
}
}
throw new InvalidActionError();
}
Expand Down
37 changes: 32 additions & 5 deletions modules/client/src/internal/client/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
permissionWithConditionParamsToContract,
} from "../utils";
import { Contract } from "@ethersproject/contracts";
import { erc20ContractAbi } from "../abi/erc20";
import { abi as ERC20_ABI } from "@openzeppelin/contracts/build/contracts/ERC20.json";
import { abi as ERC721_ABI } from "@openzeppelin/contracts/build/contracts/ERC721.json";
import {
hexToBytes,
InvalidAddressError,
Expand All @@ -38,6 +39,7 @@ import {
LIVE_CONTRACTS,
TokenType,
} from "@aragon/sdk-client-common";
import { Interface } from "@ethersproject/abi";

/**
* Encoding module the SDK Generic Client
Expand Down Expand Up @@ -247,7 +249,8 @@ export class ClientEncoding extends ClientCore implements IClientEncoding {
}
to = resolvedAddress;
}

let iface: Interface;
let data: string;
switch (params.type) {
case TokenType.NATIVE:
return { to, value: params.amount, data: new Uint8Array() };
Expand All @@ -256,11 +259,11 @@ export class ClientEncoding extends ClientCore implements IClientEncoding {
throw new InvalidAddressError();
}

const iface = new Contract(
iface = new Contract(
params.tokenAddress,
erc20ContractAbi,
ERC20_ABI,
).interface;
const data = iface.encodeFunctionData("transfer", [
data = iface.encodeFunctionData("transfer", [
params.recipientAddressOrEns,
params.amount,
]);
Expand All @@ -269,6 +272,30 @@ export class ClientEncoding extends ClientCore implements IClientEncoding {
value: BigInt(0),
data: hexToBytes(data),
};
case TokenType.ERC721:
if (
!params.tokenAddress || !params.daoAddressOrEns ||
!params.recipientAddressOrEns
) {
throw new InvalidAddressError();
}
iface = new Contract(
params.tokenAddress,
ERC721_ABI,
).interface;
data = iface.encodeFunctionData(
"safeTransferFrom(address,address,uint256)",
[
params.daoAddressOrEns, // from
params.recipientAddressOrEns, // to
params.tokenId, // tokenId
],
);
return {
to: params.tokenAddress,
value: BigInt(0),
data: hexToBytes(data),
};
default:
throw new NotImplementedError("Token type not supported");
}
Expand Down
4 changes: 2 additions & 2 deletions modules/client/src/internal/client/estimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from "@aragon/sdk-common";
import { AddressZero } from "@ethersproject/constants";
import { Contract } from "@ethersproject/contracts";
import { erc20ContractAbi } from "../abi/erc20";
import { abi as ERC20_ABI } from "@openzeppelin/contracts/build/contracts/ERC20.json";
import {
CreateDaoParams,
DepositParams,
Expand Down Expand Up @@ -149,7 +149,7 @@ export class ClientEstimation extends ClientCore implements IClientEstimation {

const contract = new Contract(
params.tokenAddress,
erc20ContractAbi,
ERC20_ABI,
signer,
);
return contract.estimateGas.approve(
Expand Down
Loading

0 comments on commit 1b4f360

Please sign in to comment.