From 290525f69a4b22ec085e49b5101fd261afce660d Mon Sep 17 00:00:00 2001 From: Kirill Kuznetcov Date: Thu, 11 Jun 2026 21:47:44 +0400 Subject: [PATCH 1/2] feat(instructions): add Controls.onlyTxOriginTokenBalanceNonZero OPCode and Instruction --- .../swap-vm/instructions/controls/index.ts | 1 + ...-token-balance-non-zero-args-coder.test.ts | 34 +++++++++++++++++++ ...rigin-token-balance-non-zero-args-coder.ts | 22 ++++++++++++ ...y-tx-origin-token-balance-non-zero-args.ts | 29 ++++++++++++++++ .../swap-vm/instructions/controls/opcodes.ts | 11 ++++++ .../swap-vm/src/swap-vm/instructions/index.ts | 1 + .../swap-vm/programs/aqua-program-builder.ts | 15 ++++++++ .../swap-vm/strategies/aqua-amm-strategy.ts | 8 +++++ .../strategies/aqua-pegged-amm-strategy.ts | 4 +++ .../strategies/aqua-xyc-amm-strategy.ts | 4 +++ 10 files changed, 129 insertions(+) create mode 100644 typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.test.ts create mode 100644 typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.ts create mode 100644 typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args.ts diff --git a/typescript/swap-vm/src/swap-vm/instructions/controls/index.ts b/typescript/swap-vm/src/swap-vm/instructions/controls/index.ts index 4fc5a5e..a88f07a 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/controls/index.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/controls/index.ts @@ -5,6 +5,7 @@ export { JumpArgs } from './jump-args' export { JumpIfTokenArgs } from './jump-if-token-args' export { DeadlineArgs } from './deadline-args' export { OnlyTakerTokenBalanceNonZeroArgs } from './only-taker-token-balance-non-zero-args' +export { OnlyTxOriginTokenBalanceNonZeroArgs } from './only-tx-origin-token-balance-non-zero-args' export { OnlyTakerTokenBalanceGteArgs } from './only-taker-token-balance-gte-args' export { OnlyTakerTokenSupplyShareGteArgs } from './only-taker-token-supply-share-gte-args' export { SaltArgs } from './salt-args' diff --git a/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.test.ts b/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.test.ts new file mode 100644 index 0000000..8d1d745 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.test.ts @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import { describe, it, expect } from 'vitest' +import { Address, HexString } from '@1inch/sdk-core' +import { OnlyTxOriginTokenBalanceNonZeroArgs } from './only-tx-origin-token-balance-non-zero-args' +import { OnlyTxOriginTokenBalanceNonZeroArgsCoder } from './only-tx-origin-token-balance-non-zero-args-coder' + +describe('OnlyTxOriginTokenBalanceNonZeroArgsCoder', () => { + const coder = new OnlyTxOriginTokenBalanceNonZeroArgsCoder() + const USDC = new Address('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48') + + it('should encode and decode args', () => { + const args = new OnlyTxOriginTokenBalanceNonZeroArgs(USDC) + + const encoded = coder.encode(args) + expect(encoded.toString().toLowerCase()).toBe(USDC.toString().toLowerCase()) + + const decoded = coder.decode(encoded) + expect(decoded.token.toString().toLowerCase()).toBe(USDC.toString().toLowerCase()) + }) + + it('should use static decode method', () => { + const encoded = new HexString(USDC.toString()) + const decoded = OnlyTxOriginTokenBalanceNonZeroArgs.decode(encoded) + expect(decoded.token.toString().toLowerCase()).toBe(USDC.toString().toLowerCase()) + }) + + it('should convert to JSON', () => { + const args = new OnlyTxOriginTokenBalanceNonZeroArgs(USDC) + expect(args.toJSON()).toEqual({ + token: USDC.toString(), + }) + }) +}) diff --git a/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.ts b/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.ts new file mode 100644 index 0000000..5dd6072 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args-coder.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import { BytesBuilder, BytesIter, add0x } from '@1inch/byte-utils' +import { Address, HexString } from '@1inch/sdk-core' +import { OnlyTxOriginTokenBalanceNonZeroArgs } from './only-tx-origin-token-balance-non-zero-args' +import type { IArgsCoder } from '../types' + +export class OnlyTxOriginTokenBalanceNonZeroArgsCoder implements IArgsCoder { + encode(args: OnlyTxOriginTokenBalanceNonZeroArgs): HexString { + const builder = new BytesBuilder() + builder.addAddress(args.token.toString()) + + return new HexString(add0x(builder.asHex())) + } + + decode(data: HexString): OnlyTxOriginTokenBalanceNonZeroArgs { + const iter = BytesIter.HexString(data.toString()) + const token = new Address(iter.nextUint160()) + + return new OnlyTxOriginTokenBalanceNonZeroArgs(token) + } +} diff --git a/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args.ts b/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args.ts new file mode 100644 index 0000000..5208689 --- /dev/null +++ b/typescript/swap-vm/src/swap-vm/instructions/controls/only-tx-origin-token-balance-non-zero-args.ts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LicenseRef-Degensoft-SwapVM-1.1 + +import type { Address, HexString } from '@1inch/sdk-core' +import { OnlyTxOriginTokenBalanceNonZeroArgsCoder } from './only-tx-origin-token-balance-non-zero-args-coder' +import type { IArgsCoder, IArgsData } from '../types' + +/** + * Arguments for checking if tx.origin holds any amount of a token + * @see https://github.com/1inch/swap-vm/blob/main/src/instructions/Controls.sol#L10 + **/ +export class OnlyTxOriginTokenBalanceNonZeroArgs implements IArgsData { + public static readonly CODER: IArgsCoder = + new OnlyTxOriginTokenBalanceNonZeroArgsCoder() + + constructor(public readonly token: Address) {} + + /** + * Decodes hex data into OnlyTxOriginTokenBalanceNonZeroArgs instance + **/ + static decode(data: HexString): OnlyTxOriginTokenBalanceNonZeroArgs { + return OnlyTxOriginTokenBalanceNonZeroArgs.CODER.decode(data) + } + + toJSON(): Record { + return { + token: this.token.toString(), + } + } +} diff --git a/typescript/swap-vm/src/swap-vm/instructions/controls/opcodes.ts b/typescript/swap-vm/src/swap-vm/instructions/controls/opcodes.ts index 75cfec4..102fb42 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/controls/opcodes.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/controls/opcodes.ts @@ -4,6 +4,7 @@ import { JumpArgs } from './jump-args' import { JumpIfTokenArgs } from './jump-if-token-args' import { DeadlineArgs } from './deadline-args' import { OnlyTakerTokenBalanceNonZeroArgs } from './only-taker-token-balance-non-zero-args' +import { OnlyTxOriginTokenBalanceNonZeroArgs } from './only-tx-origin-token-balance-non-zero-args' import { OnlyTakerTokenBalanceGteArgs } from './only-taker-token-balance-gte-args' import { OnlyTakerTokenSupplyShareGteArgs } from './only-taker-token-supply-share-gte-args' import { SaltArgs } from './salt-args' @@ -38,6 +39,16 @@ export const onlyTakerTokenBalanceNonZero: Opcode = + new Opcode( + Symbol('Controls.onlyTxOriginTokenBalanceNonZero'), + OnlyTxOriginTokenBalanceNonZeroArgs.CODER, + ) + /** * Requires taker to hold at least specified amount of token * @see https://github.com/1inch/swap-vm/blob/main/src/instructions/Controls.sol#L86 diff --git a/typescript/swap-vm/src/swap-vm/instructions/index.ts b/typescript/swap-vm/src/swap-vm/instructions/index.ts index d36f8da..9b8496c 100644 --- a/typescript/swap-vm/src/swap-vm/instructions/index.ts +++ b/typescript/swap-vm/src/swap-vm/instructions/index.ts @@ -160,4 +160,5 @@ export const aquaInstructions: Opcode[] = [ fee.aquaDynamicProtocolFeeAmountInXD, // 31 peggedSwap.peggedSwapGrowPriceRange2D, // 32 extruction.extruction, // 33 + controls.onlyTxOriginTokenBalanceNonZero, // 34 ] as const diff --git a/typescript/swap-vm/src/swap-vm/programs/aqua-program-builder.ts b/typescript/swap-vm/src/swap-vm/programs/aqua-program-builder.ts index de832db..4445b0c 100644 --- a/typescript/swap-vm/src/swap-vm/programs/aqua-program-builder.ts +++ b/typescript/swap-vm/src/swap-vm/programs/aqua-program-builder.ts @@ -91,6 +91,21 @@ export class AquaProgramBuilder extends ProgramBuilder { return this } + /** + * Requires tx.origin to hold any amount of specified token (supports NFTs) + **/ + public onlyTxOriginTokenBalanceNonZero( + data: DataFor, + ): this { + super.add( + controls.onlyTxOriginTokenBalanceNonZero.createIx( + new controls.OnlyTxOriginTokenBalanceNonZeroArgs(data.token), + ), + ) + + return this + } + /** * Requires taker to hold at least specified amount of token **/ diff --git a/typescript/swap-vm/src/swap-vm/strategies/aqua-amm-strategy.ts b/typescript/swap-vm/src/swap-vm/strategies/aqua-amm-strategy.ts index 77ae83b..5390b89 100644 --- a/typescript/swap-vm/src/swap-vm/strategies/aqua-amm-strategy.ts +++ b/typescript/swap-vm/src/swap-vm/strategies/aqua-amm-strategy.ts @@ -12,6 +12,8 @@ export abstract class AquaAMMStrategy { receiver: Address } + accessToken?: Address + salt?: bigint protected constructor() {} @@ -34,6 +36,12 @@ export abstract class AquaAMMStrategy { return this } + public withTxOriginAccessToken(token: Address): this { + this.accessToken = token + + return this + } + public withSalt(salt: bigint): this { this.salt = salt diff --git a/typescript/swap-vm/src/swap-vm/strategies/aqua-pegged-amm-strategy.ts b/typescript/swap-vm/src/swap-vm/strategies/aqua-pegged-amm-strategy.ts index 62a706f..d5ed9f3 100644 --- a/typescript/swap-vm/src/swap-vm/strategies/aqua-pegged-amm-strategy.ts +++ b/typescript/swap-vm/src/swap-vm/strategies/aqua-pegged-amm-strategy.ts @@ -24,6 +24,10 @@ export class AquaPeggedAmmStrategy extends AquaAMMStrategy { public build(): SwapVmProgram { const builder = new AquaProgramBuilder() + if (this.accessToken) { + builder.onlyTxOriginTokenBalanceNonZero({ token: this.accessToken }) + } + if (this.protocolFee) { const data = fee.ProtocolFeeArgs.fromBps(this.protocolFee.bps, this.protocolFee.receiver) builder.add(fee.aquaProtocolFeeAmountInXD.createIx(data)) diff --git a/typescript/swap-vm/src/swap-vm/strategies/aqua-xyc-amm-strategy.ts b/typescript/swap-vm/src/swap-vm/strategies/aqua-xyc-amm-strategy.ts index 705fd9e..7dbb13e 100644 --- a/typescript/swap-vm/src/swap-vm/strategies/aqua-xyc-amm-strategy.ts +++ b/typescript/swap-vm/src/swap-vm/strategies/aqua-xyc-amm-strategy.ts @@ -42,6 +42,10 @@ export class AquaXYCAmmStrategy extends AquaAMMStrategy { public build(): SwapVmProgram { const builder = new AquaProgramBuilder() + if (this.accessToken) { + builder.onlyTxOriginTokenBalanceNonZero({ token: this.accessToken }) + } + if (this.protocolFee) { const data = fee.ProtocolFeeArgs.fromBps(this.protocolFee.bps, this.protocolFee.receiver) builder.add(fee.aquaProtocolFeeAmountInXD.createIx(data)) From e2b4d6c15676f3c47d5776cf45d4aa06d8d01158 Mon Sep 17 00:00:00 2001 From: Edvard Gzraryan Date: Thu, 18 Jun 2026 17:28:24 +0400 Subject: [PATCH 2/2] chore: update AquaSwapVMRouter version to v1.0.1 and contract addresses --- typescript/swap-vm/README.md | 24 +++++++------- .../swap-vm/src/swap-vm-contract/constants.ts | 32 +++++++++++-------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/typescript/swap-vm/README.md b/typescript/swap-vm/README.md index 6a7e23f..4c9f59e 100644 --- a/typescript/swap-vm/README.md +++ b/typescript/swap-vm/README.md @@ -523,18 +523,18 @@ The SDK includes pre-configured contract addresses of `AquaSwapVMRouter` for the | Network | Chain ID | Address | |---------|----------|---------| -| Ethereum | 1 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://etherscan.io/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| BNB Chain | 56 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://bscscan.com/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Polygon | 137 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://polygonscan.com/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Arbitrum | 42161 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://arbiscan.io/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Avalanche | 43114 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](http://snowscan.xyz/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Gnosis | 100 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://gnosisscan.io/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Coinbase Base | 8453 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://basescan.org/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Optimism | 10 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://optimistic.etherscan.io/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| zkSync Era | 324 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://era.zksync.network/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Linea | 59144 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://lineascan.build/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Unichain | 1301 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://uniscan.xyz/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | -| Sonic | 146 | [0xdfd05fe230bfe7b212878414270c72c8345506fa](https://sonicscan.org/address/0xdfd05fe230bfe7b212878414270c72c8345506fa) | +| Ethereum | 1 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://etherscan.io/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| BNB Chain | 56 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://bscscan.com/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Polygon | 137 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://polygonscan.com/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Arbitrum | 42161 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://arbiscan.io/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Avalanche | 43114 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](http://snowscan.xyz/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Gnosis | 100 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://gnosisscan.io/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Coinbase Base | 8453 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://basescan.org/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Optimism | 10 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://optimistic.etherscan.io/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| zkSync Era | 324 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://era.zksync.network/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Linea | 59144 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://lineascan.build/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Unichain | 1301 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://uniscan.xyz/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | +| Sonic | 146 | [0x3c4758979ec30ca45857cabc2462a70699ed790e](https://sonicscan.org/address/0x3c4758979ec30ca45857cabc2462a70699ed790e) | Access addresses using: diff --git a/typescript/swap-vm/src/swap-vm-contract/constants.ts b/typescript/swap-vm/src/swap-vm-contract/constants.ts index f0d1b70..14104c7 100644 --- a/typescript/swap-vm/src/swap-vm-contract/constants.ts +++ b/typescript/swap-vm/src/swap-vm-contract/constants.ts @@ -8,22 +8,26 @@ import { Address, NetworkEnum } from '@1inch/sdk-core' * * Deployed with next params * - name = `AquaSwapVMRouter` - * - version = `1.0.0` + * - version = `1.0.1` * - * @see https://github.com/1inch/swap-vm/blob/8cc4c467374959af9efdb6e2b67d32d3c1083e1e/src/routers/AquaSwapVMRouter.sol#L11 + * v1.0.1 is opcode-compatible with v1.0.0 (identical instruction ordering for + * indices 0..32) and additionally registers the `onlyTxOriginTokenBalanceNonZero` + * (tx.origin access-token / KYC gate) opcode. + * + * @see https://github.com/1inch/swap-vm/blob/19cbd44/src/routers/AquaSwapVMRouter.sol#L11 * @see "../swap-vm/programs/aqua-program-builder" */ export const AQUA_SWAP_VM_CONTRACT_ADDRESSES: Record = { - [NetworkEnum.ETHEREUM]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.BINANCE]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.POLYGON]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.ARBITRUM]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.AVALANCHE]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.GNOSIS]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.COINBASE]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.OPTIMISM]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.ZKSYNC]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.LINEA]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.UNICHAIN]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), - [NetworkEnum.SONIC]: new Address('0xdfd05fe230bfe7b212878414270c72c8345506fa'), + [NetworkEnum.ETHEREUM]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.BINANCE]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.POLYGON]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.ARBITRUM]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.AVALANCHE]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.GNOSIS]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.COINBASE]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.OPTIMISM]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.ZKSYNC]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.LINEA]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.UNICHAIN]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), + [NetworkEnum.SONIC]: new Address('0x3c4758979ec30ca45857cabc2462a70699ed790e'), }