Skip to content

Commit

Permalink
Merge pull request #6 from argentlabs/fix/offchain-sessions-v5
Browse files Browse the repository at this point in the history
fix: support offchain sessions for v5
  • Loading branch information
bluecco authored Apr 4, 2024
2 parents a8038e1 + bb5bd0d commit 385a9fd
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@argent/x-shared": "^1.5.0",
"minimalistic-assert": "^1.0.1",
"starknet": "6.2.0",
"starknet5": "npm:starknet@5.25.0",
"starknet4": "npm:starknet@4.22.0"
},
"lint-staged": {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./utils"
export * from "./offchainSessionUtils"
export { SessionAccount } from "./account"
export { OffchainSessionAccountV5 } from "./offchainSessionAccountV5"
export { OffchainSessionAccount } from "./offchainSessionAccount"
export { OffchainSessionAccount as default } from "./offchainSessionAccount"
159 changes: 159 additions & 0 deletions src/offchainSessionAccountV5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import {
Abi,
Account,
AccountInterface,
Call,
CallData,
InvocationsDetails,
InvocationsSignerDetails,
InvokeFunctionResponse,
ProviderInterface,
ProviderOptions,
Signature,
SignerInterface,
TransactionType,
num,
stark,
} from "starknet"
import { ensureArray } from "./ensureArray"
import { OffchainSessionCall } from "./interface"

const OFFCHAIN_SESSION_ENTRYPOINT = "use_offchain_session"

export class OffchainSessionAccountV5
extends Account
implements AccountInterface
{
constructor(
providerOrOptions: ProviderOptions | ProviderInterface,
address: string,
pkOrSigner: Uint8Array | string | SignerInterface,
public sessionSignature: Signature,
public account: AccountInterface,
) {
super(providerOrOptions, address, pkOrSigner, "1")
}

private async sessionToCall(
dappSignature: Signature,
): Promise<OffchainSessionCall> {
const signature = stark.formatSignature(this.sessionSignature)
const formattedDappSignature = stark.formatSignature(dappSignature)

return {
contractAddress: this.address,
entrypoint: OFFCHAIN_SESSION_ENTRYPOINT,
calldata: CallData.compile({
signer: await this.signer.getPubKey(),
token1: formattedDappSignature[0],
token2: formattedDappSignature[1],
// owner
token3: signature[0],
token4: signature[1],
// cosigner
token5: signature[2],
token6: signature[3],
}),
}
}

private async extendCallsBySession(
calls: Call[],
dappSignature: Signature,
): Promise<OffchainSessionCall[]> {
const sessionCall = await this.sessionToCall(dappSignature)
return [sessionCall, ...calls]
}

/**
* Invoke execute function in account contract
*
* [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/gateway/gateway_client.py#L13-L17)
*
* @param calls - one or more calls to be executed
* @param abis - one or more abis which can be used to display the calls
* @param transactionsDetail - optional transaction details
* @returns a confirmation of invoking a function on the starknet contract
*/
public async execute(
calls: Call | Call[],
abis: Abi[] | undefined = undefined,
transactionsDetail: InvocationsDetails = {},
): Promise<InvokeFunctionResponse> {
const transactions = ensureArray(calls)

const version = "0x1" as const
const chainId = await this.getChainId()

const nonce = num.toHex(transactionsDetail.nonce ?? (await this.getNonce()))

let maxFee = transactionsDetail.maxFee

if (!maxFee) {
try {
const sim = await this.simulateTransaction(
[
{
type: TransactionType.INVOKE,
payload: calls,
},
],
{
skipValidate: true,
nonce,
},
)

const [estimation] = sim
const { fee_estimation } = estimation
const overall_fee = fee_estimation.overall_fee
maxFee = estimation.suggestedMaxFee ?? overall_fee
} catch (e) {
// fallback
maxFee = (
await this.getSuggestedFee(
{
type: TransactionType.INVOKE,
payload: calls,
},
{
skipValidate: true,
nonce,
},
)
).suggestedMaxFee
}
}

const signerDetails: InvocationsSignerDetails = {
walletAddress: this.account.address,
nonce,
maxFee,
version,
chainId,
cairoVersion: this.cairoVersion ?? "1",
}

const dappSignature = await this.signer.signTransaction(
transactions,
signerDetails,
abis,
)

const transactionsWithSession = await this.extendCallsBySession(
transactions,
dappSignature,
)

/*
need to use the same values for transactionDetails (nonce, maxFee, version)
otherwise the cosign would fail most of the times
since maxFee could change in a very short time if calculated in both this package and in webwallet
*/
return this.account?.execute(transactionsWithSession, abis, {
nonce,
maxFee: maxFee.toString(),
version,
})
}
}
86 changes: 86 additions & 0 deletions src/offchainSessionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,92 @@ export interface OffchainRequestSession {
allowedMethods: OffchainSessionAllowedMethods[]
}

export async function createOffchainSessionV5(
session: OffchainRequestSession,
account: AccountInterface,
gasFees: GasFees,
version = "1",
): Promise<Signature> {
const { sessionKey, expirationTime, allowedMethods } = session
const chainId = await account.getChainId()
const signature = await account.signMessage({
domain: {
name: "ArgentSession",
chainId,
version,
},
types: {
Session: [
{
name: "accountAddress",
type: "felt",
},
{
name: "sessionKey",
type: "felt",
},
{
name: "expirationTime",
type: "felt",
},
{
name: "gasFees",
type: "TokenSpending",
},
{
name: "allowedMethods",
type: "AllowedMethod*",
},
],
TokenSpending: [
{
name: "tokenAddress",
type: "felt",
},
{
name: "maximumAmount",
type: "u256",
},
],
AllowedMethod: [
{
name: "contractAddress",
type: "felt",
},
{
name: "method",
type: "felt",
},
],
u256: [
{
name: "low",
type: "felt",
},
{
name: "high",
type: "felt",
},
],
StarkNetDomain: [
{ name: "name", type: "felt" },
{ name: "chainId", type: "felt" },
{ name: "version", type: "felt" },
],
Message: [{ name: "message", type: "felt" }],
},
primaryType: "Session",
message: {
accountAddress: account.address,
sessionKey,
expirationTime,
gasFees,
allowedMethods,
},
})
return signature
}

export async function createOffchainSession(
wallet: StarknetWindowObject,
session: OffchainRequestSession,
Expand Down

0 comments on commit 385a9fd

Please sign in to comment.