Skip to content

Commit

Permalink
Add W5 wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
TrueCarry committed Jul 5, 2024
1 parent 5adba18 commit 59ae5c4
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 37 deletions.
5 changes: 5 additions & 0 deletions src/components/TonConnect/TonConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ export async function sendTonConnectStartMessage(
.store(storeStateInit(wallet.wallet.init as unknown as StateInit))
.endCell()
break
case 'v5R1':
stateInit = beginCell()
.store(storeStateInit(wallet.wallet.init as unknown as StateInit))
.endCell()
break
default:
throw new Error('Unknown wallet type!')
}
Expand Down
5 changes: 3 additions & 2 deletions src/components/WalletGenerator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function AddWalletPopup() {

// const typeRef = useRef<HTMLSelectElement>(null)
const subwalletIdRef = useRef<HTMLInputElement>(null)
const [walletType, setWalletType] = useState('v4R2')
const [walletType, setWalletType] = useState('v5R1')
const [walletAddress, setWalletAddress] = useState('')

const saveWallet = async (e: MouseEvent) => {
Expand All @@ -192,7 +192,7 @@ function AddWalletPopup() {
}
await CreateNewKeyWallet({
type: walletType as WalletType,
subwalletId: parseInt(subwalletIdRef.current?.value || '', 10),
subwalletId: BigInt(subwalletIdRef.current?.value || ''),
keyId: selectedKey?.id.get() || 0,
walletAddress: saveWalletAddress,
})
Expand Down Expand Up @@ -220,6 +220,7 @@ function AddWalletPopup() {
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="v5R1">W5</SelectItem>
<SelectItem value="v4R2">v4R2</SelectItem>
<SelectItem value="v3R2">v3R2</SelectItem>
<SelectItem value="highload">Highload V2</SelectItem>
Expand Down
2 changes: 1 addition & 1 deletion src/components/WalletsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function WalletRow({ wallet, isSelected }: { wallet: IWallet; isSelected: boolea
<CardDescription>
Balance: {balance ? parseFloat(balance) / 10 ** 9 : 0} TON
</CardDescription>
<CardDescription>Subwallet ID: {wallet.subwalletId}</CardDescription>
<CardDescription>Subwallet ID: {wallet.subwalletId.toString()}</CardDescription>
</CardHeader>

<CardContent>
Expand Down
7 changes: 7 additions & 0 deletions src/contracts/w5/WalletV5R1.source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Cell } from '@ton/core'

export const WalletV5R1CodeBoc =
'b5ee9c720101010100230008420220834b7b72b112147e1b2fb457b84e74d1a30f04f737d4f62a668e9552d2b72f'
// 'b5ee9c7201010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f3412' beta

export const WalletV5R1CodeCell = Cell.fromBoc(Buffer.from(WalletV5R1CodeBoc, 'hex'))[0]
20 changes: 8 additions & 12 deletions src/contracts/w5/WalletV5R1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export class WalletV5 implements Contract {
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(Opcodes.auth_extension, 32)
.storeUint(0, 64) // query id
.storeSlice(opts.body.beginParse())
.endCell(),
})
Expand All @@ -172,12 +173,7 @@ export class WalletV5 implements Contract {
}

async sendExternalSignedMessage(provider: ContractProvider, body: Cell) {
await provider.external(
beginCell()
// .storeUint(Opcodes.auth_signed, 32) // Is signed inside message
.storeSlice(body.beginParse())
.endCell()
)
await provider.external(body)
}

async sendExternal(provider: ContractProvider, body: Cell) {
Expand All @@ -202,15 +198,15 @@ export class WalletV5 implements Contract {
async getIsSignatureAuthAllowed(provider: ContractProvider) {
const state = await provider.getState()
if (state.state.type === 'active') {
const res = await provider.get('get_is_signature_auth_allowed', [])
const res = await provider.get('is_signature_allowed', [])
return res.stack.readNumber()
} else {
return -1
}
}

async getWalletId(provider: ContractProvider) {
const result = await provider.get('get_wallet_id', [])
const result = await provider.get('get_subwallet_id', [])
return WalletId.deserialize(result.stack.readBigNumber())
}

Expand All @@ -227,14 +223,14 @@ export class WalletV5 implements Contract {

const dict: Dictionary<bigint, bigint> = Dictionary.loadDirect(
Dictionary.Keys.BigUint(256),
Dictionary.Values.BigInt(8),
Dictionary.Values.BigInt(1),
extensions
)

return dict.keys().map((key) => {
const wc = dict.get(key)!
const addressHex = key ^ (wc + 1n)
return Address.parseRaw(`${wc}:${addressHex.toString(16)}`)
const wc = this.address.workChain
const addressHex = key
return Address.parseRaw(`${wc}:${addressHex.toString(16).padStart(64, '0')}`)
})
}
}
135 changes: 135 additions & 0 deletions src/contracts/w5/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint-disable no-useless-constructor */
import { Address, beginCell, Cell, MessageRelaxed, SendMode, storeMessageRelaxed } from '@ton/core'

export class ActionSendMsg {
public static readonly tag = 0x0ec3c86d

public readonly tag = ActionSendMsg.tag

constructor(
public readonly mode: SendMode,
public readonly outMsg: MessageRelaxed
) {}

public serialize(): Cell {
return beginCell()
.storeUint(this.tag, 32)
.storeUint(this.mode | SendMode.IGNORE_ERRORS, 8)
.storeRef(beginCell().store(storeMessageRelaxed(this.outMsg)).endCell())
.endCell()
}
}

export class ActionAddExtension {
public static readonly tag = 0x02

public readonly tag = ActionAddExtension.tag

constructor(public readonly address: Address) {}

public serialize(): Cell {
return beginCell().storeUint(this.tag, 8).storeAddress(this.address).endCell()
}
}

export class ActionRemoveExtension {
public static readonly tag = 0x03

public readonly tag = ActionRemoveExtension.tag

constructor(public readonly address: Address) {}

public serialize(): Cell {
return beginCell().storeUint(this.tag, 8).storeAddress(this.address).endCell()
}
}

export class ActionSetSignatureAuthAllowed {
public static readonly tag = 0x04

public readonly tag = ActionSetSignatureAuthAllowed.tag

constructor(public readonly allowed: boolean) {}

public serialize(): Cell {
return beginCell()
.storeUint(this.tag, 8)
.storeUint(this.allowed ? 1 : 0, 1)
.endCell()
}
}

export type OutAction = ActionSendMsg
export type ExtendedAction =
| ActionAddExtension
| ActionRemoveExtension
| ActionSetSignatureAuthAllowed

export function isExtendedAction(action: OutAction | ExtendedAction): action is ExtendedAction {
return (
action.tag === ActionAddExtension.tag ||
action.tag === ActionRemoveExtension.tag ||
action.tag === ActionSetSignatureAuthAllowed.tag
)
}

function packActionsListOut(actions: (OutAction | ExtendedAction)[]): Cell {
if (actions.length === 0) {
return beginCell().endCell()
}

const [action, ...rest] = actions

if (isExtendedAction(action)) {
throw new Error('Actions bust be in an order: all extended actions, all out actions')
}

return beginCell()
.storeRef(packActionsListOut(rest))
.storeSlice(action.serialize().beginParse())
.endCell()
}

function packExtendedActions(extendedActions: ExtendedAction[]): Cell {
const first = extendedActions[0]
const rest = extendedActions.slice(1)
let builder = beginCell().storeSlice(first.serialize().beginParse())
if (rest.length > 0) {
builder = builder.storeRef(packExtendedActions(extendedActions.slice(1)))
}
return builder.endCell()
}

function packActionsListExtended(actions: (OutAction | ExtendedAction)[]): Cell {
const extendedActions: ExtendedAction[] = []
const outActions: OutAction[] = []
actions.forEach((action) => {
if (isExtendedAction(action)) {
extendedActions.push(action)
} else {
outActions.push(action)
}
})

let builder = beginCell()
if (outActions.length === 0) {
builder = builder.storeUint(0, 1)
} else {
builder = builder.storeMaybeRef(packActionsListOut(outActions.slice().reverse()))
}
if (extendedActions.length === 0) {
builder = builder.storeUint(0, 1)
} else {
const first = extendedActions[0]
const rest = extendedActions.slice(1)
builder = builder.storeUint(1, 1).storeSlice(first.serialize().beginParse())
if (rest.length > 0) {
builder = builder.storeRef(packExtendedActions(rest))
}
}
return builder.endCell()
}

export function packActionsList(actions: (OutAction | ExtendedAction)[]): Cell {
return packActionsListExtended(actions)
}
10 changes: 5 additions & 5 deletions src/store/walletsListState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,17 @@ export async function saveKeyAndWallets(
{
type: 'v4R2',
key_id: newWallet.id,
subwallet_id: 698983191,
subwallet_id: '698983191',
},
{
type: 'v3R2',
key_id: newWallet.id,
subwallet_id: 698983191,
subwallet_id: '698983191',
},
{
type: 'highload',
key_id: newWallet.id,
subwallet_id: 1,
subwallet_id: '1',
},
]

Expand All @@ -163,7 +163,7 @@ export async function CreateNewKeyWallet({
walletAddress,
}: {
type: WalletType
subwalletId: number
subwalletId: bigint
keyId: number
walletAddress: string | null
}) {
Expand All @@ -172,7 +172,7 @@ export async function CreateNewKeyWallet({
.insert({
type,
key_id: keyId,
subwallet_id: subwalletId,
subwallet_id: subwalletId.toString(),
wallet_address: walletAddress,
})
.returning('*')
Expand Down
15 changes: 13 additions & 2 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Address, MessageRelaxed, SendMode, ContractProvider, Cell } from '
import type { WalletContractV4, WalletContractV3R2 } from '@ton/ton'
import { KeyPair } from '@ton/crypto'
import { HighloadWalletV3 } from '@/contracts/highload-wallet-v3/HighloadWalletV3'
import { WalletV5 } from '@/contracts/w5/WalletV5R1'

export type OpenedContract<T> = {
[P in keyof T]: P extends `get${string}` | `send${string}`
Expand Down Expand Up @@ -42,6 +43,16 @@ export interface ITonWalletV4 {
subwalletId: number
}

export interface ITonWalletV5 {
type: 'v5R1'
address: Address
wallet: OpenedContract<WalletV5>
getExternalMessageCell: GetExternalMessageCell
key: EncryptedWalletData
id: number
subwalletId: bigint
}

export interface ITonHighloadWalletV2 {
type: 'highload'
address: Address
Expand Down Expand Up @@ -87,7 +98,7 @@ export interface ITonExternalWallet {
id: string
}

export type ITonWallet = ITonWalletV3 | ITonWalletV4
export type ITonWallet = ITonWalletV3 | ITonWalletV4 | ITonWalletV5
export type IHighloadWalletV2 = ITonHighloadWalletV2 | ITonHighloadWalletV2R2
export type IHighloadWalletV3 = ITonHighloadWalletV3
export type IMultisigWallet = ITonMultisigWalletV2V4R2
Expand All @@ -108,6 +119,6 @@ export interface SavedWallet {
id: number
type: WalletType
key_id: number
subwallet_id: number
subwallet_id: string
wallet_address?: string | null
}
Loading

0 comments on commit 59ae5c4

Please sign in to comment.