Skip to content

feat: Implement SNIP29 #1377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from

Conversation

florian-bellotti
Copy link

Motivation and Resolution

This PR introduces a Provider that enables direct interaction with a Paymaster service and integrates it into the Account class to simplify its usage.

The implementation follows the SNIP-29 specification:
📄 SNIP-29 on GitHub

⚠️ Note: This may not be the final commit for full SNIP-29 integration.
However, I’d like to start gathering feedback early to ensure that the implementation aligns with the starknet.js architecture and coding conventions.

@florian-bellotti florian-bellotti force-pushed the feature/snip29 branch 2 times, most recently from a8ebf8c to f34bfde Compare April 9, 2025 07:32
@PhilippeR26
Copy link
Collaborator

Seems very very early to propose a PR on this subject...

@0xMentorNotAPseudo
Copy link

Seems very very early to propose a PR on this subject...

Not very very early at all - we’re actively finalizing SNIP-29, with StarkWare involved and supportive.
Proactive PRs = faster feedback = better standards. What’s the issue?

cc @leo-starkware

@leo-starkware
Copy link

Seems very very early to propose a PR on this subject...

Not very very early at all - we’re actively finalizing SNIP-29, with StarkWare involved and supportive. Proactive PRs = faster feedback = better standards. What’s the issue?

cc @leo-starkware

Agreed with @0xMentorNotAPseudo - Starknet v0.14 means that the topic of paymaster becomes more important than ever and starknet.js is a key player for ecosystem adoption. I'm all for making it happen!

@tabaktoni tabaktoni self-assigned this Apr 22, 2025
@PhilippeR26
Copy link
Collaborator

PhilippeR26 commented Apr 23, 2025

When I try await account.paymaster.getSupportedTokensAndPrices(); (Mainnet & Testnet), I have :

request: {
    method: 'paymaster_getSupportedTokensAndPrices',
    params: undefined
  },
  baseError: { code: -32601, message: 'Method not found' }

Is your PR up-to-date?
Are the urls currently working?

@florian-bellotti
Copy link
Author

We have updated this pull request to align with the latest SNIP-29 changes.

Notes:

  • Two Paymaster RPC URLs are provided in the constants. Both are valid, but currently only the Sepolia instance is live.
  • A full example of Paymaster usage is available in www/doc/guides/paymaster.md.

@PhilippeR26
Copy link
Collaborator

Is a non-SNIP-9 compatible account able to use SNIP-29 features?

@PhilippeR26
Copy link
Collaborator

When testing account.execute, I have this error :

Error: Unexpected token '<', "<html>
<h"... is not valid JSON
    at _PaymasterRpc.errorHandler (/home/edmond/Documents/starknet/starknet.js/dist/index.js:8202:13)
    at _PaymasterRpc.fetchEndpoint (/home/edmond/Documents/starknet/starknet.js/dist/index.js:8213:12)

Seems that the AVNU backend is reporting an error message in HTML, not in JSON format. So, Starknet.js is not able to decode and throw a documented Error.
Seems you have to change something in your backend.

@florian-bellotti
Copy link
Author

Is a non-SNIP-9 compatible account able to use SNIP-29 features?

No. accounts must be compatible with snip 9

@florian-bellotti
Copy link
Author

  • Two Paymaster RPC URLs are provided in the constants. Both are valid, but currently only the Sepolia instance is live.

Two Paymaster RPC URLs are provided in the constants. Both are valid, but currently only the Sepolia instance is live.

@PhilippeR26
Copy link
Collaborator

  • Two Paymaster RPC URLs are provided in the constants. Both are valid, but currently only the Sepolia instance is live.

Two Paymaster RPC URLs are provided in the constants. Both are valid, but currently only the Sepolia instance is live.

The problem is not to reach an URL. I can ask to Sepolia URL, using

const res = await account0.paymaster.isAvailable();
const supported = await account0.paymaster.getSupportedTokens();

It works. No problem.
As written above, the problem is the format of errors reported by the backend.

@PhilippeR26
Copy link
Collaborator

Is a non-SNIP-9 compatible account able to use SNIP-29 features?

No. accounts must be compatible with snip 9

So, it should

  • be documented in the guide.
  • be checked prior to try to proceed. We have already a method for this check:
    /**
    * Verify if an account is compatible with SNIP-9 outside execution, and with which version of this standard.
    * @returns {OutsideExecutionVersion} Not compatible, V1, V2.
    * @example
    * ```typescript
    * const result = myAccount.getSnip9Version();
    * // result = "V1"
    * ```
    */
    public async getSnip9Version(): Promise<OutsideExecutionVersion> {
    if (await supportsInterface(this, this.address, SNIP9_V2_INTERFACE_ID)) {
    return OutsideExecutionVersion.V2;
    }
    if (await supportsInterface(this, this.address, SNIP9_V1_INTERFACE_ID)) {
    return OutsideExecutionVersion.V1;
    }
    // Account does not support either version 2 or version 1
    return OutsideExecutionVersion.UNSUPPORTED;
    }

@PhilippeR26
Copy link
Collaborator

@penovicp ,
You should check how fetch is handled in src/paymaster/rpc.ts.
Seems to be in clash with the Starknet.js strategy for V7 you are currently implementing.

@PhilippeR26
Copy link
Collaborator

PhilippeR26 commented May 5, 2025

I tried to test, this afternoon, in Testnet.
4 tokens are listed as usable to pay fees : ETH, STRK, USDC, SWAY.
When I try to execute a tx with each of these token, I have :

request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: { execution_error: 'invalid typed data' }
  }

and when using sponsored, I have :

request: {
  method: 'paymaster_buildTransaction',
  params: { transaction: [Object], parameters: [Object] }
},
baseError: {
  code: 163,
  message: 'An error occurred (UNKNOWN_ERROR)',
  data: 'api key is invalid'
}

The content of my account :

image

So, what should work, and what should not?

@florian-bellotti
Copy link
Author

I tried to test, this afternoon, in Testnet. 4 tokens are listed as usable to pay fees : ETH, STRK, USDC, SWAY. When I try to execute a tx with each of these token, I have :

request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: { execution_error: 'invalid typed data' }
  }

and when using sponsored, I have :

request: {
  method: 'paymaster_buildTransaction',
  params: { transaction: [Object], parameters: [Object] }
},
baseError: {
  code: 163,
  message: 'An error occurred (UNKNOWN_ERROR)',
  data: 'api key is invalid'
}

The content of my account :

image

So, what should work, and what should not?

We had an issue on the paymaster service. The bug has been fix. You can test again the paymaster in classic fee mode. Thanks

@PhilippeR26
Copy link
Collaborator

PhilippeR26 commented May 6, 2025

Well, messages are different, but result is the same :

  • SWAY:
request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: {
      execution_error: 'execution error starknet error Transaction execution has failed:\n' +
        '0: Error in the called contract (contract address: 0x01e63177836bca5b37de3e3211c1b541c6da829edc87a482f1f351f269732b3a, class hash: 0x029927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):\n' +
        'Execution failed. Failure reason:\n' +
        "(0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x0 (''), 0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x1, 0x45524332303a20696e73756666696369656e742062616c616e6365 ('ERC20: insufficient balance'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')).\n"
    }
  }
  • ETH & STRK :
request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: {
      execution_error: 'execution error Error occurred while creating a new object: Connection refused (os error 111)'
    }
  }
  • USDC :
request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: {
      execution_error: 'execution error starknet error Transaction execution has failed:\n' +
        '0: Error in the called contract (contract address: 0x01e63177836bca5b37de3e3211c1b541c6da829edc87a482f1f351f269732b3a, class hash: 0x029927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):\n' +
        'Execution failed. Failure reason:\n' +
        "(0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x0 (''), 0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x1, 0x753235365f737562204f766572666c6f77 ('u256_sub Overflow'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')).\n"
    }
  }

@florian-bellotti
Copy link
Author

20: insufficient balan

Well, messages are different, but result is the same :

  • SWAY:
request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: {
      execution_error: 'execution error starknet error Transaction execution has failed:\n' +
        '0: Error in the called contract (contract address: 0x01e63177836bca5b37de3e3211c1b541c6da829edc87a482f1f351f269732b3a, class hash: 0x029927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):\n' +
        'Execution failed. Failure reason:\n' +
        "(0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x0 (''), 0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x1, 0x45524332303a20696e73756666696369656e742062616c616e6365 ('ERC20: insufficient balance'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')).\n"
    }
  }
  • ETH & STRK :
request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: {
      execution_error: 'execution error Error occurred while creating a new object: Connection refused (os error 111)'
    }
  }
  • USDC :
request: {
    method: 'paymaster_executeTransaction',
    params: { transaction: [Object], parameters: [Object] }
  },
  baseError: {
    code: 156,
    message: 'An error occurred (TRANSACTION_EXECUTION_ERROR)',
    data: {
      execution_error: 'execution error starknet error Transaction execution has failed:\n' +
        '0: Error in the called contract (contract address: 0x01e63177836bca5b37de3e3211c1b541c6da829edc87a482f1f351f269732b3a, class hash: 0x029927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):\n' +
        'Execution failed. Failure reason:\n' +
        "(0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x0 (''), 0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/multicall-failed'), 0x1, 0x753235365f737562204f766572666c6f77 ('u256_sub Overflow'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')).\n"
    }
  }

I just fixed the issue you had with ETH and STRK.
As for SWAY and USDC, the error comes from the token contracts themselves — they indicate that you don't have enough tokens.

@PhilippeR26
Copy link
Collaborator

I have enough SWAY & USDC in my 2 test accounts.
I suspect a problem due to non standard decimals for these tokens.
A build reponse for USDC is fee:

 {
  gas_token_price_in_strk: 232170n,
  estimated_fee_in_strk: 9561392373527680n,
  estimated_fee_in_gas_token: 41182721167798078n,
  suggested_max_fee_in_strk: 47806961867638400n,
  suggested_max_fee_in_gas_token: 205913605838990394n
}

estimated_fee_in_gas_token: 41182721167798078n is 41182721167.798078 USDC (6 decimals).
For sure, I have not enough USDC tokens in my account... 🥴

@florian-bellotti
Copy link
Author

I have enough SWAY & USDC in my 2 test accounts. I suspect a problem due to non standard decimals for these tokens. A build reponse for USDC is fee:

 {
  gas_token_price_in_strk: 232170n,
  estimated_fee_in_strk: 9561392373527680n,
  estimated_fee_in_gas_token: 41182721167798078n,
  suggested_max_fee_in_strk: 47806961867638400n,
  suggested_max_fee_in_gas_token: 205913605838990394n
}

estimated_fee_in_gas_token: 41182721167798078n is 41182721167.798078 USDC (6 decimals). For sure, I have not enough USDC tokens in my account... 🥴

We had an issue on the sepolia price feed. It's now fixed. Thanks

@PhilippeR26
Copy link
Collaborator

Hello,
Seems better.
Nevertheless, I have a doubt about gas_token_price_in_strk. It's not at all consistent with AVNU DAPP:

  • ETH
    gas_token_price_in_strk: 1770809404985164300000n
    Meaning:
Price of 1 ETH = 1770.8094049851643 STRK
Price of 1 STRK = 0.0005647135130324079 ETH

AVNU is saying: 1 ETH = 4555 STRK
Capture d’écran du 2025-05-07 09-30-19
( Braavos account is a Testnet account)

  • USDC
    gas_token_price_in_strk: 232170382590321130n
    Meaning:
Price of 1 USDC = 0.23217038259032113 STRK
Price of 1 STRK = 4.307181600180938 USDC

AVNU is saying: 1 USDC = 5.02 STRK
Capture d’écran du 2025-05-07 09-31-50

  • SWAY
    gas_token_price_in_strk: 0.001941805068259707n
    Meaning:
Price of 1 SWAY = 0.001941805068259707 STRK
Price of 1 STRK = 514.9847512223379 SWAY

AVNU is not able to quote SWAY -> STRK
Capture d’écran du 2025-05-07 09-33-35

To have STRK at 4.3 USDC is a beautiful dream, but it's problematic to check that prices proposed by SNIP-29 are consistent...
Is it possible to have gas_token_price_in_strk more consistent with AVNU swap values?

@florian-bellotti
Copy link
Author

Do you have any feedback regarding the PR ? Does the implementation align with the starknet.js architecture and coding conventions ?

@PhilippeR26
Copy link
Collaborator

@tabaktoni & @penovicp will answer on this subject.
My personal feeling is that all the code related to SNIP-29 should be more isolated.
I think also that something is missing about estimation of fees. We have already this kind of things: https://starknetjs.com/docs/next/guides/estimate_fees#estimateinvokefee. I think that DAPP devs would like to have something as simple for paymaster.
I am currently writing a small test DAPP, to have a better feedback of the DX.

@tabaktoni
Copy link
Member

For now we can mege it as it is, than we will need to create separate PR referencing to this one to isolate implementation so that we have clean solution.

  1. solution
    My general idea is to create a baseAccount from an existing account that is a pure account implementation.
    Then create an account extending baseAccount with service paymaster extension

  2. solution
    Use mixin as for starknetid on provider, but this has some bundling side-effects in class recreation and refference.

@PhilippeR26
Copy link
Collaborator

PhilippeR26 commented May 11, 2025

Hello,
I built a test DAPP, and tested transactions.
My Dev Experience feedback:
To solve immediately

  • WalletAccount.connect() and WalletAccount.connectSilent() are the normal way to create a WalletAccount instance. The paymaster parameters are missing in the signatures of these methods.

Improvements

  • In TokenDataType, add name & symbol properties. It's very easy for the backend to add them, and frontend dev wants in all cases these data. It will save async work to the frontend.
  • Still in TokenDataType type, the address should by provided in Hex64, to avoid this kind of problem : tokendata.address===USDCaddress failing due to address in Hex63 (or less).
  • Something has to be done to estimate fees as easily as a standard transaction.

@PhilippeR26
Copy link
Collaborator

How to use deploymentData ?
Is it for accounts? contracts?

@tabaktoni tabaktoni changed the base branch from main to develop May 12, 2025 10:42
@tabaktoni
Copy link
Member

tabaktoni commented May 12, 2025

I rebased this one to develop and will merge it after exporting types to types-js and re-importing it.
Let's open a separate issue/discussion about comments mentioned here and standing tasks.

@PhilippeR26
Copy link
Collaborator

You should solve problem of WalletAccount.connect() and WalletAccount.connectSilent() described above prior to merge ...

@PhilippeR26
Copy link
Collaborator

I tried to use SNIP-29 with an account that uses ETH signature (and is SNIP-9 compatible).
The fee price of the verification of the signature is way more expensive than standard Starknet signature.
With default suggested fees, I have an Error 156 max amount of gas token too low.
So, I tried to use the maxEstimatedFeeInGasToken parameter to increase the maxfees:

const res2 = await accountEthOZ17.execute(
    myCall,
    {
      paymaster: {
        feeMode: { mode: "default", gasToken },
        maxEstimatedFeeInGasToken: BigInt(builtUSDC.fee.suggested_max_fee_in_gas_token) * multiplyFees,
      }
    }
  );

Even with multiplyFees = 50, I have still the same error.
Is the maxEstimatedFeeInGasToken parameter taken into account?

@0xlny
Copy link

0xlny commented May 14, 2025

  • WalletAccount.connect() and WalletAccount.connectSilent() are the normal way to create a WalletAccount instance. The paymaster parameters are missing in the signatures of these methods.

Hello, WalletAccount.connect() and WalletAccount.connectSilent() now accepts paymaster parameters

@PhilippeR26
Copy link
Collaborator

Nice.
We have now something operational for DAPP transactions.

@0xlny Have you some answers about my questions above? (especially about the problem with ETH signature) Or maybe would you prefer to discuss in an other space?

For info, I tried also to deploy an account, but I have an error -32602: Invalid params: "missing field "unique" at line 1 column 373"

@0xlny
Copy link

0xlny commented May 14, 2025

I'll check now for account deployment

About the issue with ETH signature we can discuss it on TG, probably requires different gas calculation like account with 2fa for exemple

@PhilippeR26
Copy link
Collaborator

PhilippeR26 commented May 14, 2025

In Telegram at @lenny_0x ?
Mine is @PhilR26

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.

7 participants