Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Follow SEP-41 argument names in token commands regardless of contract naming - #2708

Open
fnando wants to merge 1 commit into
mainfrom
token-positional-args
Open

Follow SEP-41 argument names in token commands regardless of contract naming#2708
fnando wants to merge 1 commit into
mainfrom
token-positional-args

Conversation

@fnando

Copy link
Copy Markdown
Member

What

stellar token balance and stellar token transfer now map their arguments to the contract by position instead of by the contract's parameter names. Callers supply values in SEP-41's canonical order (transfer(from, to, amount), balance(id), decimals()) and the contract's actual parameter names no longer matter.

Why

The commands used to build an argv like transfer --from … --to … --amount … and let the generic invoke parser match those flags to the contract spec's parameter names. That only worked when the deployed contract literally named its parameters from/to/amount/id. A SEP-41-compliant contract can name them anything (sender, recipient, amt, addr, …), so the commands broke against those contracts. SEP-41 fixes the function names and argument order, not the parameter names — so mapping by position is the correct model. Doing this now keeps the two existing commands correct before more token subcommands (approve, allowance, mint, burn, …) are built on the same pattern.

Known limitations

Positional mapping requires the contract function's arity to match the SEP-41 signature exactly; a deviating arity fails with a clear error rather than sending a malformed call. Attacker-influenceable spec parameter names are sanitized before appearing in any error message, consistent with the existing invoke path.

CopilotAI balanced review requested due to automatic review settings August 31, 2026 13:25
@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXAug 31, 2026
@fnando
fnandoforce-pushed the token-positional-args branch from 25faf84 to ceefadaCompareAugust 31, 2026 13:26
@fnandofnando self-assigned this Aug 31, 2026
@fnandofnando moved this from Backlog (Not Ready) to Needs Review in DevXAug 31, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates token commands to map SEP-41 arguments by position through the generic contract invocation pipeline, supporting contracts with non-canonical parameter names.

Changes:

  • Adds positional contract argument parsing with strict arity validation.
  • Migrates token balance, decimals, and transfer calls to positional invocation.
  • Adds unit and integration coverage using a renamed-parameter token fixture.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
cmd/soroban-cli/src/commands/token/transfer.rsUses positional transfer arguments.
cmd/soroban-cli/src/commands/token/balance.rsUses positional balance and decimals arguments.
cmd/soroban-cli/src/commands/token/args.rsAdds shared positional token invocation helper.
cmd/soroban-cli/src/commands/contract/invoke.rsSupports internal positional invocations.
cmd/soroban-cli/src/commands/contract/arg_parsing.rsImplements positional parsing, arity checks, and unit tests.
cmd/crates/soroban-test/tests/it/integration/token/renamed.rsTests renamed SEP-41 parameters end to end.
cmd/crates/soroban-test/tests/it/integration/token/mod.rsRegisters the new integration tests.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/src/lib.rsDefines the renamed-parameter token fixture.
cmd/crates/soroban-test/tests/fixtures/test-wasms/token_renamed/Cargo.tomlConfigures the fixture crate.
Cargo.lockRecords the new fixture package.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI review requested due to automatic review settings August 31, 2026 13:27

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@fnando
fnando requested review from a team and leighmccullochAugust 31, 2026 13:32
Comment on lines -133 to +132
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
vec![account],

@leighmccullochleighmccullochSep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's a bit unclear to me how this works, is the idea that the fns lose the named options like --id and are no longer in the form stellar token balance --id G... and become positional, for e.g. stellar token balance G...? That deviation from the way that stellar contract invoke works seems unnecessary, since the main problem being addressed is that a contract might use different names for fields other than those defined in SEP-41, and we can solve that problem with a change that diverges much less from the contract invoke subcommand by playing a game of aliasing.

By aliasing I mean taking the arguments as resolved, and adding an alias to the each field with their name as known by SEP-41 given their ordered position in the fn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The example I use above with 'id' being the account being queried is incorrect, instead using transfer as a better example:

Today I understand the command as:

stellar token transfer --id C... --from G... --to G... --amount 2

Can we introduce aliasing, so that a contract whose invoke command looks like this because of custom argument names:

stellar contract invoke --id C... -- transfer --src G... --dst G... --amount 2

Can be used as follows via aliasing:

stellar token transfer --id C... --src G... --dst G... --amount 2
or
stellar token transfer --id C... --from G... --to G... --amount 2

Or if supporting the custom names is undesirable, simply replacing the names but keeping the use of options/flags:

stellar token transfer --id C... --from G... --to G... --amount 2

@fnandofnandoSep 2, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Because invoking a contract requires knowing the argument names as defined in the spec, we can't assume developers followed the naming as defined in SEP-41. E.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; could be defined as fn allowance(env: Env, addr: Address, consumer: Address) -> i128;, which would need to be invoked as --addr <address> --consumer <address>.

So, instead, this allows us to call the function with positional arguments instead, which then is mapped to the actual contract's names.

The command's name must be stable and will match SEP-41's spec (e.g. fn allowance(env: Env, from: Address, spender: Address) -> i128; will directly translate to --from <from> --spender <spender>).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One really big downside to positional cli's though, it's easy to mixup and misunderstand the parameters which for a token transfer can be detrimental.

For example, it's difficult to determine in the following positional cli use who is the sender and receiver:

stellar token transfer C... C... C... 2

Where-as with the use of aliasing, we retain that explicitness still fully addressing the issue of field names:

stellar token transfer --id C... --from G... --to G... --amount 2

If we want positional arguments, then I think we should do it on all invokes too rather than making this a bespoke behaviour for the token commands. The stellar contract invoke command could support both named and positional invocation. And then the token command would benefit from that and we'd have more consistency across the CLI.

Whichever way we go here, it can work for all the invoking commands, either through aliasing (which achieves the same thing) or through supporting positional on both. My preference is aliasing because I think the explicitness makes it safer.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@leighmcculloch