Skip to content

Commit bebaf6b

Browse files
Merge remote-tracking branch 'origin/main' into copilot/implement-ci-workflow-workaround
2 parents af98be6 + b3878cc commit bebaf6b

7 files changed

Lines changed: 336 additions & 10 deletions

File tree

.github/copilot-instructions.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# GitHub Copilot Instructions
2+
3+
## Pull Request and Commit Title Naming Rules
4+
5+
All pull request titles and commit messages in this repository must follow the
6+
[Conventional Commits](https://www.conventionalcommits.org/) specification. This
7+
is enforced by the `lint-pr.yaml` GitHub Actions workflow using
8+
[`amannn/action-semantic-pull-request@v4`](https://github.com/amannn/action-semantic-pull-request/tree/v4/).
9+
10+
### Format
11+
12+
```
13+
<type>[(<scope>)][!]: <subject>
14+
```
15+
16+
- **`<type>`** — required, must be one of the allowed types listed below.
17+
- **`(<scope>)`** — optional, must be one of the allowed scopes listed below when provided.
18+
- **`!`** — optional, used to indicate a **breaking change**.
19+
- **`<subject>`** — required, a short description of the change. **Must not start with an uppercase letter.**
20+
21+
### Allowed Types
22+
23+
| Type | Description |
24+
|------------|---------------------------------------------------------------------|
25+
| `feat` | A new feature |
26+
| `fix` | A bug fix |
27+
| `docs` | Documentation only changes |
28+
| `style` | Changes that do not affect the meaning of the code (formatting etc) |
29+
| `refactor` | A code change that neither fixes a bug nor adds a feature |
30+
| `perf` | A code change that improves performance |
31+
| `test` | Adding missing tests or correcting existing tests |
32+
| `build` | Changes that affect the build system or external dependencies |
33+
| `ci` | Changes to CI configuration files and scripts |
34+
| `chore` | Other changes that don't modify src or test files |
35+
| `revert` | Reverts a previous commit |
36+
37+
### Allowed Scopes (optional)
38+
39+
When a scope is provided, it must be one of:
40+
41+
- `common`
42+
- `core-sdk`
43+
- `eth-connect-sdk`
44+
- `ethers-sdk`
45+
- `ipfs-storage`
46+
- `subgraph`
47+
- `deps`
48+
- `metadata`
49+
- `react-kit`
50+
51+
### Subject Rules
52+
53+
- Must **not** start with an uppercase letter (e.g. use `add feature` not `Add feature`).
54+
- Should be a concise description in the imperative mood.
55+
56+
### Breaking Changes
57+
58+
Use the `!` suffix after the type (and optional scope) to signal a breaking change:
59+
60+
```
61+
feat!: drop support for Node 14
62+
feat(core-sdk)!: change return type of createOffer
63+
```
64+
65+
### Single-Commit PRs
66+
67+
When a pull request contains only a single commit, the commit title must also
68+
comply with the Conventional Commits format above (it is validated alongside
69+
the PR title).
70+
71+
### Valid Examples
72+
73+
```
74+
feat: add support for new offer type
75+
fix(core-sdk): resolve incorrect price calculation
76+
docs: update README with new SDK usage
77+
chore(deps): bump ethers to v6
78+
refactor(react-kit): simplify checkout flow
79+
feat(ethers-sdk)!: remove deprecated createVoucher method
80+
```
81+
82+
### Invalid Examples
83+
84+
```
85+
Add new feature ← missing type
86+
feat: Add new feature ← subject starts with uppercase 'A'
87+
feature: add new thing ← 'feature' is not an allowed type
88+
feat(ui): add button ← 'ui' is not an allowed scope
89+
```

e2e/tests/meta-tx.test.ts

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ZERO_ADDRESS } from "./../../packages/core-sdk/tests/mocks";
22
import { BigNumberish } from "@ethersproject/bignumber";
3+
import { parseEther } from "@ethersproject/units";
34
import { Wallet, BigNumber, constants } from "ethers";
45
import { OfferFieldsFragment } from "../../packages/core-sdk/src/subgraph";
56
import { mockCreateOfferArgs } from "../../packages/common/tests/mocks";
@@ -26,12 +27,19 @@ import {
2627
createOfferWithCondition,
2728
getCollectionMetadataUri,
2829
createRandomWallet,
29-
META_TX_API_ID_VOUCHER
30+
META_TX_API_ID_VOUCHER,
31+
createDisputeResolver,
32+
deployerWallet,
33+
initSellerAndBuyerSDKs
3034
} from "./utils";
3135
import { CoreSDK, forwarder } from "../../packages/core-sdk/src";
3236
import EvaluationMethod from "../../contracts/protocol-contracts/scripts/domain/EvaluationMethod";
3337
import TokenType from "../../contracts/protocol-contracts/scripts/domain/TokenType";
34-
import { AuthTokenType, GatingType } from "../../packages/common";
38+
import { AuthTokenType, GatingType, OfferCreator } from "../../packages/common";
39+
import {
40+
MSEC_PER_DAY,
41+
MSEC_PER_SEC
42+
} from "./../../packages/common/src/utils/timestamp";
3543

3644
const sellerWallet = seedWallet7; // be sure the seedWallet is not used by another test (to allow concurrent run)
3745
const sellerAddress = sellerWallet.address;
@@ -724,6 +732,198 @@ describe("meta-tx", () => {
724732
});
725733
});
726734

735+
describe("#signMetaTxCommitToBuyerOffer()", () => {
736+
test("native exchange token buyer-initiated offer", async () => {
737+
const exchangeToken = constants.AddressZero;
738+
// drFeeAmount must be 0 so the seller doesn't need to deposit funds before
739+
// committing via meta-tx (meta-tx can't carry ETH value)
740+
const drFeeAmount = "0";
741+
742+
// Create a dispute resolver with zero fee (required for meta-tx compatibility)
743+
const { fundedWallet: drFundedWallet } =
744+
await initCoreSDKWithFundedWallet(sellerWallet);
745+
const drAddress = drFundedWallet.address.toLowerCase();
746+
const { disputeResolver } = await createDisputeResolver(
747+
drFundedWallet,
748+
deployerWallet,
749+
{
750+
assistant: drAddress,
751+
admin: drAddress,
752+
treasury: drAddress,
753+
metadataUri: "",
754+
escalationResponsePeriodInMS:
755+
90 * MSEC_PER_DAY - 1 * MSEC_PER_SEC,
756+
fees: [
757+
{
758+
feeAmount: drFeeAmount,
759+
tokenAddress: exchangeToken,
760+
tokenName: "Native"
761+
}
762+
],
763+
sellerAllowList: []
764+
}
765+
);
766+
767+
// Create fresh buyer/seller wallets
768+
const {
769+
sellerCoreSDK: sellerCoreSDKBuyer,
770+
buyerCoreSDK: buyerCoreSDKBuyer,
771+
sellerWallet: sellerFundedWallet
772+
} = await initSellerAndBuyerSDKs(sellerWallet);
773+
774+
// Buyer creates a buyer-initiated offer.
775+
// sellerDeposit must be 0 so the meta-tx relayer doesn't need to forward ETH value.
776+
const buyerInitiatedOffer = await createOffer(buyerCoreSDKBuyer, {
777+
creator: OfferCreator.Buyer,
778+
quantityAvailable: 1,
779+
disputeResolverId: disputeResolver.id,
780+
exchangeToken,
781+
sellerDeposit: "0"
782+
});
783+
784+
// Buyer deposits offer.price to allow the seller to commit
785+
const buyerDepositTx = await buyerCoreSDKBuyer.depositFunds(
786+
buyerInitiatedOffer.buyerId,
787+
buyerInitiatedOffer.price,
788+
exchangeToken
789+
);
790+
await buyerCoreSDKBuyer.waitForGraphNodeIndexing(buyerDepositTx);
791+
792+
// Seller creates a seller account
793+
await createSeller(sellerCoreSDKBuyer, sellerFundedWallet.address);
794+
// No seller depositFunds needed: drFeeAmount = 0 and sellerDeposit = 0
795+
796+
const nonce = Date.now();
797+
798+
// Seller signs meta tx for commitToBuyerOffer
799+
const { r, s, v, functionName, functionSignature } =
800+
await sellerCoreSDKBuyer.signMetaTxCommitToBuyerOffer({
801+
offerId: buyerInitiatedOffer.id,
802+
sellerParams: {},
803+
nonce
804+
});
805+
806+
// Relayer executes meta tx on behalf of seller
807+
const metaTx = await sellerCoreSDKBuyer.relayMetaTransaction({
808+
functionName,
809+
functionSignature,
810+
nonce,
811+
sigR: r,
812+
sigS: s,
813+
sigV: v
814+
});
815+
const metaTxReceipt = await metaTx.wait();
816+
expect(metaTxReceipt.transactionHash).toBeTruthy();
817+
expect(BigNumber.from(metaTxReceipt.effectiveGasPrice).gt(0)).toBe(true);
818+
});
819+
820+
test("non-native exchange token buyer-initiated offer", async () => {
821+
const exchangeToken = MOCK_ERC20_ADDRESS;
822+
const drFeeAmount = parseEther("0.001");
823+
824+
// Create a dispute resolver with an ERC20 fee
825+
const { fundedWallet: drFundedWallet } =
826+
await initCoreSDKWithFundedWallet(sellerWallet);
827+
const drAddress = drFundedWallet.address.toLowerCase();
828+
const { disputeResolver } = await createDisputeResolver(
829+
drFundedWallet,
830+
deployerWallet,
831+
{
832+
assistant: drAddress,
833+
admin: drAddress,
834+
treasury: drAddress,
835+
metadataUri: "",
836+
escalationResponsePeriodInMS:
837+
90 * MSEC_PER_DAY - 1 * MSEC_PER_SEC,
838+
fees: [
839+
{
840+
feeAmount: drFeeAmount,
841+
tokenAddress: exchangeToken,
842+
tokenName: "ERC20"
843+
}
844+
],
845+
sellerAllowList: []
846+
}
847+
);
848+
849+
// Create fresh buyer/seller wallets
850+
const {
851+
sellerCoreSDK: sellerCoreSDKBuyer,
852+
buyerCoreSDK: buyerCoreSDKBuyer,
853+
sellerWallet: sellerFundedWallet,
854+
buyerWallet: buyerFundedWallet
855+
} = await initSellerAndBuyerSDKs(sellerWallet);
856+
857+
// Mint ERC20 tokens to fresh wallets (they start with 0 ERC20 balance)
858+
await ensureMintedAndAllowedTokens(
859+
[buyerFundedWallet, sellerFundedWallet],
860+
undefined,
861+
false
862+
);
863+
864+
// Buyer creates a buyer-initiated offer with ERC20 exchange token.
865+
// sellerDeposit is 0 to simplify the test setup.
866+
const buyerInitiatedOffer = await createOffer(buyerCoreSDKBuyer, {
867+
creator: OfferCreator.Buyer,
868+
quantityAvailable: 1,
869+
disputeResolverId: disputeResolver.id,
870+
exchangeToken,
871+
sellerDeposit: "0"
872+
});
873+
874+
// Buyer approves ERC20 and deposits offer.price
875+
await approveErc20Token(
876+
buyerCoreSDKBuyer,
877+
exchangeToken,
878+
buyerInitiatedOffer.price
879+
);
880+
const buyerDepositTx = await buyerCoreSDKBuyer.depositFunds(
881+
buyerInitiatedOffer.buyerId,
882+
buyerInitiatedOffer.price,
883+
exchangeToken
884+
);
885+
await buyerCoreSDKBuyer.waitForGraphNodeIndexing(buyerDepositTx);
886+
887+
// Seller creates a seller account
888+
const seller = await createSeller(
889+
sellerCoreSDKBuyer,
890+
sellerFundedWallet.address
891+
);
892+
893+
// Seller approves ERC20 and deposits drFeeAmount into the protocol
894+
await approveErc20Token(sellerCoreSDKBuyer, exchangeToken, drFeeAmount);
895+
const sellerDepositTx = await sellerCoreSDKBuyer.depositFunds(
896+
seller.id,
897+
drFeeAmount,
898+
exchangeToken
899+
);
900+
await sellerCoreSDKBuyer.waitForGraphNodeIndexing(sellerDepositTx);
901+
902+
const nonce = Date.now();
903+
904+
// Seller signs meta tx for commitToBuyerOffer
905+
const { r, s, v, functionName, functionSignature } =
906+
await sellerCoreSDKBuyer.signMetaTxCommitToBuyerOffer({
907+
offerId: buyerInitiatedOffer.id,
908+
sellerParams: {},
909+
nonce
910+
});
911+
912+
// Relayer executes meta tx on behalf of seller
913+
const metaTx = await sellerCoreSDKBuyer.relayMetaTransaction({
914+
functionName,
915+
functionSignature,
916+
nonce,
917+
sigR: r,
918+
sigS: s,
919+
sigV: v
920+
});
921+
const metaTxReceipt = await metaTx.wait();
922+
expect(metaTxReceipt.transactionHash).toBeTruthy();
923+
expect(BigNumber.from(metaTxReceipt.effectiveGasPrice).gt(0)).toBe(true);
924+
});
925+
});
926+
727927
describe("#signMetaTxRedeemVoucher()", () => {
728928
test("non-native exchange token offer", async () => {
729929
const commitTx = await buyerCoreSDK.commitToOffer(offerToCommit.id);

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bosonprotocol/core-sdk",
3-
"version": "1.47.0-alpha.0",
3+
"version": "1.47.0-alpha.2",
44
"description": "Facilitates interaction with the contracts and subgraphs of the Boson Protocol",
55
"main": "./dist/cjs/index.js",
66
"module": "./dist/esm/index.js",

packages/core-sdk/src/meta-tx/handler.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
UpdateSellerArgs,
1212
OptInToSellerUpdateArgs,
1313
envConfigs,
14-
abis
14+
abis,
15+
SellerOfferArgs
1516
} from "@bosonprotocol/common";
1617
import { storeMetadataOnTheGraph } from "../offers/storage";
1718
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
@@ -24,7 +25,8 @@ import {
2425
} from "../accounts/interface";
2526
import {
2627
bosonExchangeCommitHandlerIface,
27-
bosonExchangeHandlerIface
28+
bosonExchangeHandlerIface,
29+
encodeCommitToBuyerOffer
2830
} from "../exchanges/interface";
2931
import {
3032
bosonOfferHandlerIface,
@@ -944,6 +946,22 @@ export async function signMetaTxCommitToConditionalOffer(
944946
};
945947
}
946948

949+
export async function signMetaTxCommitToBuyerOffer(
950+
args: BaseMetaTxArgs & {
951+
offerId: BigNumberish;
952+
sellerParams: SellerOfferArgs;
953+
}
954+
): Promise<SignedMetaTx> {
955+
const functionName =
956+
"commitToBuyerOffer(uint256,(uint256,(address[],uint256[]),address))";
957+
958+
return signMetaTx({
959+
...args,
960+
functionName,
961+
functionSignature: encodeCommitToBuyerOffer(args.offerId, args.sellerParams)
962+
});
963+
}
964+
947965
export async function signMetaTxCancelVoucher(
948966
args: BaseMetaTxArgs & {
949967
exchangeId: BigNumberish;

0 commit comments

Comments
 (0)