Skip to content

Conversation

ashuralyk
Copy link
Contributor

Description

Current spore sdk implementation didn't achieve a zero-transfer-fee feature when transfering spore, this feature should come up with a little change on low-level transaction fee completion logic

Copy link

changeset-bot bot commented Jul 30, 2025

🦋 Changeset detected

Latest commit: dacf39e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@ckb-ccc/core Patch
@ckb-ccc/spore Patch
@ckb-ccc/eip6963 Patch
@ckb-ccc/joy-id Patch
@ckb-ccc/lumos-patches Patch
@ckb-ccc/nip07 Patch
@ckb-ccc/okx Patch
@ckb-ccc/rei Patch
@ckb-ccc/shell Patch
@ckb-ccc/ssri Patch
@ckb-ccc/udt Patch
@ckb-ccc/uni-sat Patch
@ckb-ccc/utxo-global Patch
@ckb-ccc/xverse Patch
@ckb-ccc/ccc Patch
ckb-ccc Patch
@ckb-ccc/connector Patch
@ckb-ccc/connector-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for apiccc ready!

Name Link
🔨 Latest commit dacf39e
🔍 Latest deploy log https://app.netlify.com/projects/apiccc/deploys/68b93ad4e44e2200088e0e12
😎 Deploy Preview https://deploy-preview-241--apiccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 82 (🟢 up 10 from production)
Accessibility: 90 (no change from production)
Best Practices: 92 (no change from production)
SEO: 82 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for liveccc ready!

Name Link
🔨 Latest commit dacf39e
🔍 Latest deploy log https://app.netlify.com/projects/liveccc/deploys/68b93ad47b2ee50008a997cd
😎 Deploy Preview https://deploy-preview-241--liveccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 8 (🔴 down 1 from production)
Accessibility: 88 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for docsccc ready!

Name Link
🔨 Latest commit dacf39e
🔍 Latest deploy log https://app.netlify.com/projects/docsccc/deploys/68b93ad45561910008af62bc
😎 Deploy Preview https://deploy-preview-241--docsccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 71 (🟢 up 10 from production)
Accessibility: 88 (no change from production)
Best Practices: 92 (no change from production)
SEO: 92 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for appccc ready!

Name Link
🔨 Latest commit dacf39e
🔍 Latest deploy log https://app.netlify.com/projects/appccc/deploys/68b93ad44f8aac0008e3db80
😎 Deploy Preview https://deploy-preview-241--appccc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 68 (🔴 down 15 from production)
Accessibility: 88 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @ashuralyk, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I've implemented a significant feature to enable zero-transfer fees within the Spore SDK. This involved introducing a 'margin' concept to cell outputs, allowing them to carry excess capacity that can be consumed to cover transaction fees. The core transaction completion logic has been updated to prioritize using this margin, providing a more flexible and efficient way to handle fees without requiring additional inputs or change outputs. This change streamlines the transaction process, particularly for Spore transfers, by making it possible to execute them without explicit fee deductions from the sender's primary balance.

Highlights

  • Zero-Transfer Fee Mechanism: I've introduced a new 'margin' concept to CellOutput objects, allowing them to hold extra capacity beyond their minimum occupied size. This margin can then be used to cover transaction fees.
  • Capacity Margin Management: I've added a margin method to the CellOutput class and new getOutputCapacityMargin and getOutputsCapacityMargin methods to the Transaction class. These enable the calculation and retrieval of available margin capacity within transaction outputs.
  • Flexible Fee Payment: The completeFee and related methods in the Transaction class now accept a payFeeFromMargin option. When enabled, the transaction will attempt to pay fees by reducing the margin of specified outputs, eliminating the need for additional inputs or change outputs.
  • Environment Variable Refactoring: I've refactored environment variable handling by removing env.example.js and introducing .example.env and packages/spore/.env.js. The dotenv package has been integrated to load these variables, improving configuration management.
  • New Test Coverage: A new test case, zeroFeeTransfer.test.ts, has been added to thoroughly test and demonstrate the new zero-transfer fee functionality, ensuring its correct behavior.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a valuable feature for enabling zero-transfer fees by utilizing capacity margins in output cells. The overall approach is sound, but I've identified two critical bugs in the implementation of margin calculation and fee payment that could lead to invalid transactions. Additionally, I've suggested a refactoring for one of the new methods to enhance its readability and maintainability. Addressing these issues will ensure the feature is robust and reliable.

}

margin(dataLen: NumLike = 0): Num {
return this.capacity - fixedPointFrom(this.occupiedSize) + numFrom(dataLen);
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The calculation for the cell's margin appears to be incorrect. The capacity cost of the data (dataLen) should be subtracted from the total capacity, but it is currently being added. This will result in an incorrect margin calculation, which could cause issues with the fee payment logic.

Suggested change
return this.capacity - fixedPointFrom(this.occupiedSize) + numFrom(dataLen);
return this.capacity - fixedPointFrom(this.occupiedSize) - numFrom(dataLen);

break;
} else {
leastExtraCapacity -= margin;
output.capacity = fixedPointFrom(output.occupiedSize);
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

When consuming the entire margin of an output to pay for fees, the new capacity is set to fixedPointFrom(output.occupiedSize). This is incorrect as it doesn't account for the capacity required for the cell's data, which will lead to an invalid transaction if the cell contains data. The capacity should be set to the minimum required for both the structure and its data.

              output.capacity = fixedPointFrom(
                output.occupiedSize + bytesFrom(tx.outputsData[index] ?? "0x").length,
              );

Comment on lines 1676 to 1964
getOutputsCapacityMargin(
filter:
| Array<Omit<ScriptLike, "args"> & { args?: HexLike }>
| Array<number> = [],
): {
marginCapacity: Num;
collectedOutputIndices: Array<number>;
} {
let marginCapacity: Num = numFrom(0);
const collectedOutputIndices: Array<number> = [];

const marginOutputs = this.outputs
.map((output, i) => {
return {
script: output.type,
margin: this.getOutputCapacityMargin(i),
index: i,
};
})
.filter((output) => output.margin > 0);

if (filter.length === 0) {
marginCapacity = marginOutputs.reduce((acc, output) => {
collectedOutputIndices.push(output.index);
return acc + output.margin;
}, numFrom(0));
} else if (typeof filter[0] === "number") {
// filter is array of output indices
marginCapacity = marginOutputs.reduce((acc, output) => {
if ((filter as Array<number>).includes(output.index)) {
collectedOutputIndices.push(output.index);
return acc + output.margin;
}
return acc;
}, numFrom(0));
} else {
// filter is array of ScriptLike
marginCapacity = marginOutputs.reduce((acc, output) => {
if (
(filter as Array<ScriptLike>).some(
(script) =>
hexFrom(script.codeHash) === output.script?.codeHash &&
hashTypeFrom(script.hashType) === output.script?.hashType &&
(script.args === undefined ||
output.script?.args.startsWith(hexFrom(script.args))),
)
) {
collectedOutputIndices.push(output.index);
return acc + output.margin;
}
return acc;
}, numFrom(0));
}

return {
marginCapacity,
collectedOutputIndices,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The getOutputsCapacityMargin method has a lot of duplicated code across the different filter conditions. This can be refactored to be more concise and maintainable by using a single loop and a predicate function to handle the filtering logic.

  getOutputsCapacityMargin(
    filter:
      | Array<Omit<ScriptLike, "args"> & { args?: HexLike }>
      | Array<number> = [],
  ): {
    marginCapacity: Num;
    collectedOutputIndices: Array<number>;
  } {
    const marginOutputs = this.outputs
      .map((output, i) => ({
        script: output.type,
        margin: this.getOutputCapacityMargin(i),
        index: i,
      }))
      .filter((output) => output.margin > 0);

    let predicate: (output: (typeof marginOutputs)[0]) => boolean;

    if (filter.length === 0) {
      predicate = () => true;
    } else if (typeof filter[0] === "number") {
      const indices = new Set(filter as number[]);
      predicate = (output) => indices.has(output.index);
    } else {
      const scripts = filter as Array<ScriptLike>;
      predicate = (output) =>
        scripts.some(
          (script) =>
            hexFrom(script.codeHash) === output.script?.codeHash &&
            hashTypeFrom(script.hashType) === output.script?.hashType &&
            (script.args === undefined ||
              output.script?.args.startsWith(hexFrom(script.args))),
        );
    }

    let marginCapacity: Num = numFrom(0);
    const collectedOutputIndices: Array<number> = [];

    for (const output of marginOutputs) {
      if (predicate(output)) {
        marginCapacity += output.margin;
        collectedOutputIndices.push(output.index);
      }
    }

    return {
      marginCapacity,
      collectedOutputIndices,
    };
  }

@ashuralyk
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a 'margin' feature to enable zero-fee transfers for Spores. The changes are well-implemented across the core transaction logic and the Spore package. The core logic in transaction.ts is updated to handle margin capacity in outputs and to pay fees from this margin. The Spore-specific logic correctly utilizes this new core functionality. I've included a few suggestions to improve code clarity, error handling, and testing practices.

Comment on lines 1711 to 1718
const collectedOutputIndices: Array<number> = [];
const marginCapacity = marginOutputs.reduce((acc, output) => {
if (predicate(output)) {
collectedOutputIndices.push(output.index);
return acc + output.margin;
}
return acc;
}, numFrom(0));
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The reduce function here has a side effect of populating collectedOutputIndices. This can make the code harder to understand and maintain. It would be clearer to first filter the outputs and then use reduce only for summation.

Suggested change
const collectedOutputIndices: Array<number> = [];
const marginCapacity = marginOutputs.reduce((acc, output) => {
if (predicate(output)) {
collectedOutputIndices.push(output.index);
return acc + output.margin;
}
return acc;
}, numFrom(0));
const collectedOutputs = marginOutputs.filter(predicate);
const collectedOutputIndices = collectedOutputs.map(
(output) => output.index,
);
const marginCapacity = collectedOutputs.reduce(
(acc, output) => acc + output.margin,
numFrom(0),
);

Comment on lines 2070 to 2072
throw new Error(
`Insufficient margin capacity to pay fee, need ${leastExtraCapacity}, actual ${margins.marginCapacity}`,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Throwing a generic Error makes it difficult for callers to programmatically handle this specific error case. Consider creating and throwing a custom error class, for example ErrorTransactionInsufficientMarginCapacity, similar to how ErrorTransactionInsufficientCapacity is used elsewhere in this file. This would improve error handling and make the API more robust.

Comment on lines +39 to +43
console.log(JSON.stringify(JsonRpcTransformers.transactionFrom(tx)));

// Send transaction
const txHash = await signer.client.sendTransaction(tx);
console.log(txHash);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The test uses console.log to output results. While this is useful for debugging, in automated tests, it's better to use assertions to verify the expected behavior. For example, you could assert that txHash is a valid hex string of the correct length. This makes the test more robust and self-verifying.

@ashuralyk ashuralyk force-pushed the feat/dob-zero-transfer-fee branch from 39ea5f6 to e103def Compare July 30, 2025 13:28
Copy link
Member

@Hanssen0 Hanssen0 left a comment

Choose a reason for hiding this comment

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

I don't think injecting this logic into Transaction.complete is a good idea. This makes things over-complicated. Do we have other ways to implement the pay fee by margin feature?

@ashuralyk ashuralyk force-pushed the feat/dob-zero-transfer-fee branch from df9b96a to 6d25c00 Compare September 4, 2025 01:36
@ashuralyk
Copy link
Contributor Author

ashuralyk commented Sep 4, 2025

/gemini review

Copy link
Contributor

It looks like you've used /gemini without a specific command. If you intended to request a code review, please use /gemini review. If you wanted a summary of the pull request, use /gemini summary. You can also type /gemini help to see a list of all available commands.

@ashuralyk
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a 'margin' feature to enable zero-fee transfers for Spores. The changes include adding logic to createSpore to set a capacity margin and to transferSpore to use this margin to pay for transaction fees. New helper methods are added to the core Transaction class to support this. The changes are well-structured and include a new test case. However, I've found a few issues in the implementation logic that could prevent the feature from working as intended, including a potential crash, a logic error in how the margin is applied, and an incorrect fee rate check. I've also spotted a minor code redundancy.

Comment on lines +1783 to +1786
addOutput(
cellOrOutputLike: CellAnyLike | CellOutputLike,
outputDataLike?: HexLike | null,
): number;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This addOutput overload appears to be a duplicate of the one defined just below it (lines 1787-1790) and the one that was already present. This redundancy can be confusing and should be removed to improve code clarity.

ashuralyk and others added 2 commits September 4, 2025 12:38
solve gemini high priority issues

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants