mirror of
https://github.com/aljazceru/ark.git
synced 2026-02-23 12:12:49 +01:00
Add support for Out Of Round txs (#359)
* [common] rework address encoding * new address encoding * replace offchain address by vtxo output key in DB * merge migrations files into init one * fix txbuilder fixtures * fix transaction events * OOR scheme * fix conflicts * [sdk] OOR * update WASM wrappers * revert renaming * revert API changes * update parser.go * fix vtxosToTxsCovenantless * add settled and spent in Utxo and Transaction * Fixes (#5) * Revert unneeded changes and rename claim to settle * Revert changes to wasm and rename claim to settle --------- Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,7 @@ type ArkClient interface {
|
||||
ctx context.Context, addr string, amount uint64, withExpiryCoinselect bool,
|
||||
) (string, error)
|
||||
SendAsync(ctx context.Context, withExpiryCoinselect bool, receivers []Receiver) (string, error)
|
||||
Claim(ctx context.Context) (string, error)
|
||||
Settle(ctx context.Context) (string, error)
|
||||
ListVtxos(ctx context.Context) (spendable, spent []client.Vtxo, err error)
|
||||
Dump(ctx context.Context) (seed string, err error)
|
||||
GetTransactionHistory(ctx context.Context) ([]types.Transaction, error)
|
||||
|
||||
@@ -270,6 +270,27 @@ func (a *arkClient) ping(
|
||||
return ticker.Stop
|
||||
}
|
||||
|
||||
func (a *arkClient) ListVtxos(
|
||||
ctx context.Context,
|
||||
) (spendableVtxos, spentVtxos []client.Vtxo, err error) {
|
||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, addr := range offchainAddrs {
|
||||
spendable, spent, err := a.client.ListVtxos(ctx, addr.Address)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
spendableVtxos = append(spendableVtxos, spendable...)
|
||||
spentVtxos = append(spentVtxos, spent...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getClient(
|
||||
supportedClients utils.SupportedType[utils.ClientFactory], clientType, aspUrl string,
|
||||
) (client.ASPClient, error) {
|
||||
@@ -329,3 +350,15 @@ func getWalletStore(storeType, datadir string) (walletstore.WalletStore, error)
|
||||
func getCreatedAtFromExpiry(roundLifetime int64, expiry time.Time) time.Time {
|
||||
return expiry.Add(-time.Duration(roundLifetime) * time.Second)
|
||||
}
|
||||
|
||||
func filterByOutpoints(vtxos []client.Vtxo, outpoints []client.Outpoint) []client.Vtxo {
|
||||
filtered := make([]client.Vtxo, 0, len(vtxos))
|
||||
for _, vtxo := range vtxos {
|
||||
for _, outpoint := range outpoints {
|
||||
if vtxo.Outpoint.Equals(outpoint) {
|
||||
filtered = append(filtered, vtxo)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ type Outpoint struct {
|
||||
VOut uint32
|
||||
}
|
||||
|
||||
func (o Outpoint) Equals(other Outpoint) bool {
|
||||
return o.Txid == other.Txid && o.VOut == other.VOut
|
||||
}
|
||||
|
||||
type Input struct {
|
||||
Outpoint
|
||||
Descriptor string
|
||||
@@ -95,7 +99,7 @@ type Vtxo struct {
|
||||
RoundTxid string
|
||||
ExpiresAt *time.Time
|
||||
RedeemTx string
|
||||
Pending bool
|
||||
IsOOR bool
|
||||
SpentBy string
|
||||
}
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ func (c *grpcClient) GetTransactionsStream(
|
||||
Round: &client.RoundTransaction{
|
||||
Txid: tx.Round.Txid,
|
||||
SpentVtxos: outpointsFromProto(tx.Round.SpentVtxos),
|
||||
SpendableVtxos: vtxosFromProto(tx.Round.SpendableVtxos),
|
||||
SpendableVtxos: vtxos(tx.Round.SpendableVtxos).toVtxos(),
|
||||
ClaimedBoardingUtxos: outpointsFromProto(tx.Round.ClaimedBoardingUtxos),
|
||||
},
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func (c *grpcClient) GetTransactionsStream(
|
||||
Redeem: &client.RedeemTransaction{
|
||||
Txid: tx.Redeem.Txid,
|
||||
SpentVtxos: outpointsFromProto(tx.Redeem.SpentVtxos),
|
||||
SpendableVtxos: vtxosFromProto(tx.Redeem.SpendableVtxos),
|
||||
SpendableVtxos: vtxos(tx.Redeem.SpendableVtxos).toVtxos(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -394,24 +394,3 @@ func outpointsFromProto(protoOutpoints []*arkv1.Outpoint) []client.Outpoint {
|
||||
}
|
||||
return outpoints
|
||||
}
|
||||
|
||||
func vtxosFromProto(protoVtxos []*arkv1.Vtxo) []client.Vtxo {
|
||||
vtxos := make([]client.Vtxo, len(protoVtxos))
|
||||
for i, v := range protoVtxos {
|
||||
expiresAt := time.Unix(v.ExpireAt, 0)
|
||||
vtxos[i] = client.Vtxo{
|
||||
Outpoint: client.Outpoint{
|
||||
Txid: v.Outpoint.Txid,
|
||||
VOut: v.Outpoint.Vout,
|
||||
},
|
||||
Pubkey: v.Pubkey,
|
||||
Amount: v.Amount,
|
||||
RoundTxid: v.RoundTxid,
|
||||
ExpiresAt: &expiresAt,
|
||||
RedeemTx: v.RedeemTx,
|
||||
Pending: v.Pending,
|
||||
SpentBy: v.SpentBy,
|
||||
}
|
||||
}
|
||||
return vtxos
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func (v vtxo) toVtxo() client.Vtxo {
|
||||
Amount: v.GetAmount(),
|
||||
RoundTxid: v.GetRoundTxid(),
|
||||
ExpiresAt: expiresAt,
|
||||
Pending: v.GetPending(),
|
||||
IsOOR: v.GetIsOor(),
|
||||
RedeemTx: v.GetRedeemTx(),
|
||||
SpentBy: v.GetSpentBy(),
|
||||
Pubkey: v.GetPubkey(),
|
||||
|
||||
@@ -499,7 +499,7 @@ func (a *restClient) ListVtxos(
|
||||
Amount: uint64(amount),
|
||||
RoundTxid: v.RoundTxid,
|
||||
ExpiresAt: expiresAt,
|
||||
Pending: v.Pending,
|
||||
IsOOR: v.IsOor,
|
||||
RedeemTx: v.RedeemTx,
|
||||
SpentBy: v.SpentBy,
|
||||
Pubkey: v.Pubkey,
|
||||
@@ -701,7 +701,7 @@ func vtxosFromRest(restVtxos []*models.V1Vtxo) []client.Vtxo {
|
||||
RoundTxid: v.RoundTxid,
|
||||
ExpiresAt: expiresAt,
|
||||
RedeemTx: v.RedeemTx,
|
||||
Pending: v.Pending,
|
||||
IsOOR: v.IsOor,
|
||||
SpentBy: v.SpentBy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ type V1Vtxo struct {
|
||||
// expire at
|
||||
ExpireAt string `json:"expireAt,omitempty"`
|
||||
|
||||
// is oor
|
||||
IsOor bool `json:"isOor,omitempty"`
|
||||
|
||||
// outpoint
|
||||
Outpoint *V1Outpoint `json:"outpoint,omitempty"`
|
||||
|
||||
// pending
|
||||
Pending bool `json:"pending,omitempty"`
|
||||
|
||||
// pubkey
|
||||
Pubkey string `json:"pubkey,omitempty"`
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 1000,
|
||||
Type: sdktypes.TxSent,
|
||||
IsPending: false,
|
||||
CreatedAt: time.Unix(1726054898, 0),
|
||||
},
|
||||
},
|
||||
@@ -48,7 +47,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 1000,
|
||||
Type: sdktypes.TxReceived,
|
||||
IsPending: true,
|
||||
CreatedAt: time.Unix(1726054898, 0),
|
||||
},
|
||||
{
|
||||
@@ -57,7 +55,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 2000,
|
||||
Type: sdktypes.TxReceived,
|
||||
IsPending: true,
|
||||
CreatedAt: time.Unix(1726486359, 0),
|
||||
},
|
||||
},
|
||||
@@ -72,7 +69,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 1000,
|
||||
Type: sdktypes.TxReceived,
|
||||
IsPending: false,
|
||||
CreatedAt: time.Unix(1726054898, 0),
|
||||
},
|
||||
{
|
||||
@@ -81,7 +77,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 2000,
|
||||
Type: sdktypes.TxReceived,
|
||||
IsPending: false,
|
||||
CreatedAt: time.Unix(1726486359, 0),
|
||||
},
|
||||
},
|
||||
@@ -96,7 +91,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 1000,
|
||||
Type: sdktypes.TxReceived,
|
||||
IsPending: false,
|
||||
CreatedAt: time.Unix(1726054898, 0),
|
||||
},
|
||||
{
|
||||
@@ -105,7 +99,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 2000,
|
||||
Type: sdktypes.TxReceived,
|
||||
IsPending: false,
|
||||
CreatedAt: time.Unix(1726486359, 0),
|
||||
},
|
||||
{
|
||||
@@ -114,7 +107,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
},
|
||||
Amount: 2100,
|
||||
Type: sdktypes.TxSent,
|
||||
IsPending: false,
|
||||
CreatedAt: time.Unix(1726503865, 0),
|
||||
},
|
||||
},
|
||||
@@ -138,7 +130,6 @@ func TestVtxosToTxs(t *testing.T) {
|
||||
require.Equal(t, wantTx.RedeemTxid, gotTx.RedeemTxid)
|
||||
require.Equal(t, int(wantTx.Amount), int(gotTx.Amount))
|
||||
require.Equal(t, wantTx.Type, gotTx.Type)
|
||||
require.Equal(t, wantTx.IsPending, gotTx.IsPending)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -161,16 +152,12 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) {
|
||||
Address string `json:"address"`
|
||||
Amount string `json:"amount"`
|
||||
} `json:"receiver"`
|
||||
Spent bool `json:"spent"`
|
||||
PoolTxid string `json:"poolTxid"`
|
||||
SpentBy string `json:"spentBy"`
|
||||
ExpireAt string `json:"expireAt"`
|
||||
Swept bool `json:"swept"`
|
||||
Pending bool `json:"pending"`
|
||||
PendingData struct {
|
||||
RedeemTx string `json:"redeemTx"`
|
||||
UnconditionalForfeitTxs []string `json:"unconditionalForfeitTxs"`
|
||||
} `json:"pendingData"`
|
||||
Spent bool `json:"spent"`
|
||||
PoolTxid string `json:"poolTxid"`
|
||||
SpentBy string `json:"spentBy"`
|
||||
ExpireAt string `json:"expireAt"`
|
||||
Swept bool `json:"swept"`
|
||||
RedeemTx string `json:"redeemTx"`
|
||||
} `json:"spendableVtxos"`
|
||||
SpentVtxos []struct {
|
||||
Outpoint struct {
|
||||
@@ -181,16 +168,12 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) {
|
||||
Address string `json:"address"`
|
||||
Amount string `json:"amount"`
|
||||
} `json:"receiver"`
|
||||
Spent bool `json:"spent"`
|
||||
PoolTxid string `json:"poolTxid"`
|
||||
SpentBy string `json:"spentBy"`
|
||||
ExpireAt string `json:"expireAt"`
|
||||
Swept bool `json:"swept"`
|
||||
Pending bool `json:"pending"`
|
||||
PendingData struct {
|
||||
RedeemTx string `json:"redeemTx"`
|
||||
UnconditionalForfeitTxs []string `json:"unconditionalForfeitTxs"`
|
||||
} `json:"pendingData"`
|
||||
Spent bool `json:"spent"`
|
||||
PoolTxid string `json:"poolTxid"`
|
||||
SpentBy string `json:"spentBy"`
|
||||
ExpireAt string `json:"expireAt"`
|
||||
Swept bool `json:"swept"`
|
||||
RedeemTx string `json:"redeemTx"`
|
||||
} `json:"spentVtxos"`
|
||||
}
|
||||
|
||||
@@ -216,8 +199,7 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) {
|
||||
Amount: amount,
|
||||
RoundTxid: vtxo.PoolTxid,
|
||||
ExpiresAt: &expireAt,
|
||||
RedeemTx: vtxo.PendingData.RedeemTx,
|
||||
Pending: vtxo.Pending,
|
||||
RedeemTx: vtxo.RedeemTx,
|
||||
SpentBy: vtxo.SpentBy,
|
||||
}
|
||||
}
|
||||
@@ -240,8 +222,7 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) {
|
||||
Amount: amount,
|
||||
RoundTxid: vtxo.PoolTxid,
|
||||
ExpiresAt: &expireAt,
|
||||
RedeemTx: vtxo.PendingData.RedeemTx,
|
||||
Pending: vtxo.Pending,
|
||||
RedeemTx: vtxo.RedeemTx,
|
||||
SpentBy: vtxo.SpentBy,
|
||||
}
|
||||
}
|
||||
@@ -301,9 +282,7 @@ var (
|
||||
"poolTxid": "377fa2fbd27c82bdbc095478384c88b6c75432c0ef464189e49c965194446cdf",
|
||||
"spentBy": "",
|
||||
"expireAt": "1726054928",
|
||||
"swept": false,
|
||||
"pending": false,
|
||||
"pendingData": null
|
||||
"swept": false
|
||||
}
|
||||
],
|
||||
"spentVtxos": []
|
||||
@@ -329,13 +308,7 @@ var (
|
||||
"spentBy": "",
|
||||
"expireAt": "1726054928",
|
||||
"swept": false,
|
||||
"pending": false,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
}
|
||||
],
|
||||
"spentVtxos": [
|
||||
@@ -353,8 +326,7 @@ var (
|
||||
"spentBy": "94fa598302f17f00c8881e742ec0ce2f8c8d16f3d54fe6ba0fb7d13a493d84ad",
|
||||
"expireAt": "1726054928",
|
||||
"swept": false,
|
||||
"pending": false,
|
||||
"pendingData": null
|
||||
"redeemTx": ""
|
||||
}
|
||||
]
|
||||
}`
|
||||
@@ -376,13 +348,7 @@ var (
|
||||
"spentBy": "",
|
||||
"expireAt": "1726054928",
|
||||
"swept": false,
|
||||
"pending": true,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
},
|
||||
{
|
||||
"outpoint": {
|
||||
@@ -398,13 +364,7 @@ var (
|
||||
"spentBy": "",
|
||||
"expireAt": "1726486389",
|
||||
"swept": false,
|
||||
"pending": true,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AZA9DwAAAAAAFgAU+9NJhjFhe8jX1hrXh3NvDyHZ1cYAAAAAAAEBK1g+DwAAAAAAIlEg/u5de2XiMtRxVVBT6xftYVebHYfqIIzOfuDJ7S/VjwlBFJzXKND+8dEd6/bd8oYA0u9s6GQ1iYa2ZFa3x9KzOU4lTNEVud/F3V+r2AgSlojS+tnDDy2Vn3iIQvvlChohtWpARJzBjlEkN/kTpyFEtpvP2Ui7ypevuxb9J/NUAwhYf8Pmnnj1l3WuKCSi4Fcp1O+lQjIiZlNpwY6J73q/V8Fe2kIVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA7kPYyHfHRc0bEUJLrsY26CGQQ0Wh2kV5WlmOfvpyEjxFIB1UBXV4sr8CJS3Z44vBb21IG/hArk0TQAeu6MCZ43GjrSCc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJazAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
}
|
||||
],
|
||||
"spentVtxos": []
|
||||
@@ -426,8 +386,7 @@ var (
|
||||
"spentBy": "",
|
||||
"expireAt": "1726503895",
|
||||
"swept": false,
|
||||
"pending": false,
|
||||
"pendingData": null
|
||||
"redeemTx": ""
|
||||
}
|
||||
],
|
||||
"spentVtxos": [
|
||||
@@ -445,13 +404,7 @@ var (
|
||||
"spentBy": "d6684a5b9e6939dccdf07d1f0eaf7fdd7b31de4d123e63e400d23de739800d4e",
|
||||
"expireAt": "1726054928",
|
||||
"swept": false,
|
||||
"pending": true,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
},
|
||||
{
|
||||
"outpoint": {
|
||||
@@ -467,13 +420,7 @@ var (
|
||||
"spentBy": "d6684a5b9e6939dccdf07d1f0eaf7fdd7b31de4d123e63e400d23de739800d4e",
|
||||
"expireAt": "1726486389",
|
||||
"swept": false,
|
||||
"pending": true,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AZA9DwAAAAAAFgAU+9NJhjFhe8jX1hrXh3NvDyHZ1cYAAAAAAAEBK1g+DwAAAAAAIlEg/u5de2XiMtRxVVBT6xftYVebHYfqIIzOfuDJ7S/VjwlBFJzXKND+8dEd6/bd8oYA0u9s6GQ1iYa2ZFa3x9KzOU4lTNEVud/F3V+r2AgSlojS+tnDDy2Vn3iIQvvlChohtWpARJzBjlEkN/kTpyFEtpvP2Ui7ypevuxb9J/NUAwhYf8Pmnnj1l3WuKCSi4Fcp1O+lQjIiZlNpwY6J73q/V8Fe2kIVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA7kPYyHfHRc0bEUJLrsY26CGQQ0Wh2kV5WlmOfvpyEjxFIB1UBXV4sr8CJS3Z44vBb21IG/hArk0TQAeu6MCZ43GjrSCc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJazAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
}
|
||||
]
|
||||
}`
|
||||
@@ -494,13 +441,7 @@ var (
|
||||
"spentBy": "",
|
||||
"expireAt": "1726503895",
|
||||
"swept": false,
|
||||
"pending": false,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAdOK9YzYw1ceJznqJxtRXGe0KeHj6CLcLtqLVwcbMCivAAAAAAD/////ArgLAAAAAAAAIlEgC39Vxhw3dIa4heHgFS6X4XwDl1mBggsKLVTBwF1h3qEgegEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysAAAAAAABASughgEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysIgYDjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F0YAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRTYEOuHJ0hyLBGzY8nSHpD2F1nby5/XQ5Sh2Je+cQ5Wsx0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gQNSvgaBk/1WLYqQxCKxCfv8ViVJ7vjBxvNO5tc2FEDy27V9cIrfL1jPJoVrhgPZT0GwY7dkVZS7saIKI03CbipBCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wPKiQ0JM6aw2kcUByijEbOydM3gTIVCGN/69q+dmyxcqRSCMZ4x+c3AKtTjmIHeJGqIV1ZMANqIn3rz7Deu4W6rUXa0g2BDrhydIciwRs2PJ0h6Q9hdZ28uf10OUodiXvnEOVrOswCEWjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F05AR0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAAdOK9YzYw1ceJznqJxtRXGe0KeHj6CLcLtqLVwcbMCivAAAAAAD/////AdiFAQAAAAAAFgAUlsBYsQa9BEiB8ZumuN4J50lbQIoAAAAAAAEBK6CGAQAAAAAAIlEgySS18gXEU1O0CbLcrSn7smpWfaRwDQ/rLa8nVBfbPKxBFNgQ64cnSHIsEbNjydIekPYXWdvLn9dDlKHYl75xDlazHRm5wuYH8suynExE30lwmfdDYolEyGGDvjXlwKDV3SBAZadgbU8gCDvq3XN0EeLIwGKGSAYHZRkGbAnr9ZjCHGKAQlfFNYS0af1Lz4j7Th2osVY8JJv7O736sC5NNQome0IVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA8qJDQkzprDaRxQHKKMRs7J0zeBMhUIY3/r2r52bLFypFIIxnjH5zcAq1OOYgd4kaohXVkwA2oifevPsN67hbqtRdrSDYEOuHJ0hyLBGzY8nSHpD2F1nby5/XQ5Sh2Je+cQ5Ws6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAdOK9YzYw1ceJznqJxtRXGe0KeHj6CLcLtqLVwcbMCivAAAAAAD/////ArgLAAAAAAAAIlEgC39Vxhw3dIa4heHgFS6X4XwDl1mBggsKLVTBwF1h3qEgegEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysAAAAAAABASughgEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysIgYDjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F0YAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRTYEOuHJ0hyLBGzY8nSHpD2F1nby5/XQ5Sh2Je+cQ5Wsx0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gQNSvgaBk/1WLYqQxCKxCfv8ViVJ7vjBxvNO5tc2FEDy27V9cIrfL1jPJoVrhgPZT0GwY7dkVZS7saIKI03CbipBCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wPKiQ0JM6aw2kcUByijEbOydM3gTIVCGN/69q+dmyxcqRSCMZ4x+c3AKtTjmIHeJGqIV1ZMANqIn3rz7Deu4W6rUXa0g2BDrhydIciwRs2PJ0h6Q9hdZ28uf10OUodiXvnEOVrOswCEWjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F05AR0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
}
|
||||
],
|
||||
"spentVtxos": [
|
||||
@@ -518,13 +459,7 @@ var (
|
||||
"spentBy": "d6684a5b9e6939dccdf07d1f0eaf7fdd7b31de4d123e63e400d23de739800d4e",
|
||||
"expireAt": "1726054928",
|
||||
"swept": false,
|
||||
"pending": true,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
},
|
||||
{
|
||||
"outpoint": {
|
||||
@@ -541,12 +476,7 @@ var (
|
||||
"expireAt": "1726486389",
|
||||
"swept": false,
|
||||
"pending": true,
|
||||
"pendingData": {
|
||||
"redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=",
|
||||
"unconditionalForfeitTxs": [
|
||||
"cHNidP8BAFICAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AZA9DwAAAAAAFgAU+9NJhjFhe8jX1hrXh3NvDyHZ1cYAAAAAAAEBK1g+DwAAAAAAIlEg/u5de2XiMtRxVVBT6xftYVebHYfqIIzOfuDJ7S/VjwlBFJzXKND+8dEd6/bd8oYA0u9s6GQ1iYa2ZFa3x9KzOU4lTNEVud/F3V+r2AgSlojS+tnDDy2Vn3iIQvvlChohtWpARJzBjlEkN/kTpyFEtpvP2Ui7ypevuxb9J/NUAwhYf8Pmnnj1l3WuKCSi4Fcp1O+lQjIiZlNpwY6J73q/V8Fe2kIVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA7kPYyHfHRc0bEUJLrsY26CGQQ0Wh2kV5WlmOfvpyEjxFIB1UBXV4sr8CJS3Z44vBb21IG/hArk0TQAeu6MCZ43GjrSCc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJazAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA=="
|
||||
]
|
||||
}
|
||||
"redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA="
|
||||
},
|
||||
{
|
||||
"outpoint": {
|
||||
@@ -562,8 +492,7 @@ var (
|
||||
"spentBy": "23c3a885f0ea05f7bdf83f3bf7f8ac9dc3f791ad292f4e63a6f53fa5e4935ab0",
|
||||
"expireAt": "1726503895",
|
||||
"swept": false,
|
||||
"pending": false,
|
||||
"pendingData": null
|
||||
"redeemTx": ""
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/redemption"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/types"
|
||||
@@ -251,26 +250,6 @@ func (a *covenantArkClient) listenForBoardingUtxos(
|
||||
//are multiple boarding inputs + spent vtxo with change in spendable + received in the same round
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) ListVtxos(
|
||||
ctx context.Context,
|
||||
) (spendableVtxos, spentVtxos []client.Vtxo, err error) {
|
||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, addr := range offchainAddrs {
|
||||
spendable, spent, err := a.client.ListVtxos(ctx, addr.Address)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
spendableVtxos = append(spendableVtxos, spendable...)
|
||||
spentVtxos = append(spentVtxos, spent...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) Balance(
|
||||
ctx context.Context, computeVtxoExpiration bool,
|
||||
) (*Balance, error) {
|
||||
@@ -285,14 +264,13 @@ func (a *covenantArkClient) Balance(
|
||||
|
||||
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
|
||||
for i := range offchainAddrs {
|
||||
offchainAddr := offchainAddrs[i]
|
||||
boardingAddr := boardingAddrs[i]
|
||||
redeemAddr := redeemAddrs[i]
|
||||
|
||||
go func(addr string) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
balance, amountByExpiration, err := a.getOffchainBalance(
|
||||
ctx, addr, computeVtxoExpiration,
|
||||
ctx, computeVtxoExpiration,
|
||||
)
|
||||
if err != nil {
|
||||
chRes <- balanceRes{err: err}
|
||||
@@ -303,7 +281,7 @@ func (a *covenantArkClient) Balance(
|
||||
offchainBalance: balance,
|
||||
offchainBalanceByExpiration: amountByExpiration,
|
||||
}
|
||||
}(offchainAddr.Address)
|
||||
}()
|
||||
|
||||
getDelayedBalance := func(addr string) {
|
||||
defer wg.Done()
|
||||
@@ -448,20 +426,11 @@ func (a *covenantArkClient) UnilateralRedeem(ctx context.Context) error {
|
||||
return fmt.Errorf("wallet is locked")
|
||||
}
|
||||
|
||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
||||
vtxos, err := a.getVtxos(ctx, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vtxos := make([]client.Vtxo, 0)
|
||||
for _, offchainAddr := range offchainAddrs {
|
||||
fetchedVtxos, _, err := a.client.ListVtxos(ctx, offchainAddr.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vtxos = append(vtxos, fetchedVtxos...)
|
||||
}
|
||||
|
||||
totalVtxosAmount := uint64(0)
|
||||
for _, vtxo := range vtxos {
|
||||
totalVtxosAmount += vtxo.Amount
|
||||
@@ -519,22 +488,13 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
||||
return "", fmt.Errorf("wallet is locked")
|
||||
}
|
||||
|
||||
// validate liquid address
|
||||
if _, err := address.ToOutputScript(addr); err != nil {
|
||||
return "", fmt.Errorf("invalid onchain address")
|
||||
}
|
||||
|
||||
addrNet, err := address.NetworkForAddress(addr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid onchain address: unknown network")
|
||||
}
|
||||
net := utils.ToElementsNetwork(a.Network)
|
||||
if net.Name != addrNet.Name {
|
||||
return "", fmt.Errorf("invalid onchain address: must be for %s network", net.Name)
|
||||
}
|
||||
|
||||
if isConf, _ := address.IsConfidential(addr); isConf {
|
||||
info, _ := address.FromConfidential(addr)
|
||||
addr = info.Address
|
||||
if isConf, err := address.IsConfidential(addr); err != nil || isConf {
|
||||
return "", fmt.Errorf("confidential onchain address not supported")
|
||||
}
|
||||
|
||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
||||
@@ -550,22 +510,34 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
||||
}
|
||||
|
||||
vtxos := make([]client.DescriptorVtxo, 0)
|
||||
for _, offchainAddr := range offchainAddrs {
|
||||
spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
spendableVtxos, err := a.getVtxos(ctx, false, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, vtxo := range spendableVtxos {
|
||||
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||
Vtxo: vtxo,
|
||||
Descriptor: offchainAddr.Descriptor,
|
||||
})
|
||||
for _, offchainAddr := range offchainAddrs {
|
||||
for _, v := range spendableVtxos {
|
||||
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if vtxoAddr == offchainAddr.Address {
|
||||
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||
Vtxo: v,
|
||||
Descriptor: offchainAddr.Descriptor,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||
vtxos, amount, a.Dust, withExpiryCoinselect,
|
||||
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
selectedBoardingUtxos, selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||
boardingUtxos, vtxos, amount, a.Dust, withExpiryCoinselect,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -583,7 +555,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
||||
})
|
||||
}
|
||||
|
||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||
inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingUtxos))
|
||||
|
||||
for _, coin := range selectedCoins {
|
||||
inputs = append(inputs, client.Input{
|
||||
@@ -594,8 +566,17 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
||||
Descriptor: coin.Descriptor,
|
||||
})
|
||||
}
|
||||
for _, coin := range selectedBoardingUtxos {
|
||||
inputs = append(inputs, client.Input{
|
||||
Outpoint: client.Outpoint{
|
||||
Txid: coin.Txid,
|
||||
VOut: coin.VOut,
|
||||
},
|
||||
Descriptor: coin.Descriptor,
|
||||
})
|
||||
}
|
||||
|
||||
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
|
||||
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -604,9 +585,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
||||
return "", err
|
||||
}
|
||||
|
||||
poolTxID, err := a.handleRoundStream(
|
||||
ctx, paymentID, selectedCoins, nil, "", receivers,
|
||||
)
|
||||
poolTxID, err := a.handleRoundStream(ctx, paymentID, selectedCoins, selectedBoardingUtxos, receivers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -621,36 +600,10 @@ func (a *covenantArkClient) SendAsync(
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
|
||||
myselfOffchain, boardingAddr, err := a.wallet.NewAddress(ctx, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var pendingBalance uint64
|
||||
for _, vtxo := range boardingUtxos {
|
||||
pendingBalance += vtxo.Amount
|
||||
}
|
||||
if pendingBalance == 0 {
|
||||
return "", fmt.Errorf("no funds to claim")
|
||||
}
|
||||
|
||||
receiver := client.Output{
|
||||
Address: myselfOffchain.Address,
|
||||
Amount: pendingBalance,
|
||||
}
|
||||
|
||||
return a.selfTransferAllPendingPayments(
|
||||
ctx,
|
||||
boardingUtxos,
|
||||
receiver,
|
||||
boardingAddr.Descriptor,
|
||||
)
|
||||
func (a *covenantArkClient) Settle(
|
||||
ctx context.Context,
|
||||
) (string, error) {
|
||||
return a.sendOffchain(ctx, false, nil)
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) GetTransactionHistory(
|
||||
@@ -675,13 +628,13 @@ func (a *covenantArkClient) GetTransactionHistory(
|
||||
return vtxosToTxsCovenant(config.RoundLifetime, spendableVtxos, spentVtxos, boardingTxs)
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
||||
func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]types.Utxo, error) {
|
||||
_, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utxos := []explorer.Utxo{}
|
||||
utxos := make([]types.Utxo, 0)
|
||||
for _, addr := range boardingAddrs {
|
||||
txs, err := a.explorer.GetTxs(addr.Address)
|
||||
if err != nil {
|
||||
@@ -694,11 +647,12 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer
|
||||
if tx.Status.Confirmed {
|
||||
createdAt = time.Unix(tx.Status.Blocktime, 0)
|
||||
}
|
||||
utxos = append(utxos, explorer.Utxo{
|
||||
Txid: tx.Txid,
|
||||
Vout: uint32(i),
|
||||
Amount: vout.Amount,
|
||||
CreatedAt: createdAt,
|
||||
utxos = append(utxos, types.Utxo{
|
||||
Txid: tx.Txid,
|
||||
VOut: uint32(i),
|
||||
Amount: vout.Amount,
|
||||
CreatedAt: createdAt,
|
||||
Descriptor: addr.Descriptor,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -708,13 +662,13 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer
|
||||
return utxos, nil
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
||||
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts *CoinSelectOptions) ([]types.Utxo, error) {
|
||||
_, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claimable := make([]explorer.Utxo, 0)
|
||||
claimable := make([]types.Utxo, 0)
|
||||
|
||||
for _, addr := range boardingAddrs {
|
||||
boardingScript, err := tree.ParseVtxoScript(addr.Descriptor)
|
||||
@@ -738,8 +692,25 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]ex
|
||||
now := time.Now()
|
||||
|
||||
for _, utxo := range boardingUtxos {
|
||||
u := utxo.ToUtxo(boardingTimeout)
|
||||
if opts != nil && len(opts.OutpointsFilter) > 0 {
|
||||
utxoOutpoint := client.Outpoint{
|
||||
Txid: utxo.Txid,
|
||||
VOut: utxo.Vout,
|
||||
}
|
||||
found := false
|
||||
for _, outpoint := range opts.OutpointsFilter {
|
||||
if outpoint.Equals(utxoOutpoint) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
|
||||
if u.SpendableAt.Before(now) {
|
||||
continue
|
||||
}
|
||||
@@ -906,84 +877,123 @@ func (a *covenantArkClient) sendOnchain(
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) sendOffchain(
|
||||
ctx context.Context, withExpiryCoinselect bool, receivers []Receiver,
|
||||
ctx context.Context,
|
||||
withExpiryCoinselect bool, receivers []Receiver,
|
||||
) (string, error) {
|
||||
if a.wallet.IsLocked() {
|
||||
return "", fmt.Errorf("wallet is locked")
|
||||
}
|
||||
|
||||
expectedAspPubKey := schnorr.SerializePubKey(a.AspPubkey)
|
||||
outputs := make([]client.Output, 0)
|
||||
sumOfReceivers := uint64(0)
|
||||
|
||||
// validate receivers and create outputs
|
||||
for _, receiver := range receivers {
|
||||
rcvAddr, err := common.DecodeAddress(receiver.To())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid receiver address: %s", err)
|
||||
}
|
||||
|
||||
rcvAspPubKey := schnorr.SerializePubKey(rcvAddr.Asp)
|
||||
|
||||
if !bytes.Equal(expectedAspPubKey, rcvAspPubKey) {
|
||||
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubKey), hex.EncodeToString(rcvAspPubKey))
|
||||
}
|
||||
|
||||
if receiver.Amount() < a.Dust {
|
||||
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
||||
}
|
||||
|
||||
outputs = append(outputs, client.Output{
|
||||
Address: receiver.To(),
|
||||
Amount: receiver.Amount(),
|
||||
})
|
||||
sumOfReceivers += receiver.Amount()
|
||||
}
|
||||
|
||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(offchainAddrs) <= 0 {
|
||||
return "", fmt.Errorf("no funds detected")
|
||||
}
|
||||
|
||||
expectedAspPubkey := schnorr.SerializePubKey(a.AspPubkey)
|
||||
|
||||
receiversOutput := make([]client.Output, 0)
|
||||
sumOfReceivers := uint64(0)
|
||||
|
||||
for _, receiver := range receivers {
|
||||
rcvAddr, err := common.DecodeAddress(receiver.To())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid receiver address: %s", err)
|
||||
}
|
||||
|
||||
rcvAspPubkey := schnorr.SerializePubKey(rcvAddr.Asp)
|
||||
|
||||
if !bytes.Equal(rcvAspPubkey, expectedAspPubkey) {
|
||||
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubkey), hex.EncodeToString(rcvAspPubkey))
|
||||
}
|
||||
|
||||
if receiver.Amount() < a.Dust {
|
||||
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
||||
}
|
||||
|
||||
receiversOutput = append(receiversOutput, client.Output{
|
||||
Address: receiver.To(),
|
||||
Amount: receiver.Amount(),
|
||||
})
|
||||
sumOfReceivers += receiver.Amount()
|
||||
return "", fmt.Errorf("no offchain addresses found")
|
||||
}
|
||||
|
||||
vtxos := make([]client.DescriptorVtxo, 0)
|
||||
for _, offchainAddr := range offchainAddrs {
|
||||
spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, vtxo := range spendableVtxos {
|
||||
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||
Vtxo: vtxo,
|
||||
Descriptor: offchainAddr.Descriptor,
|
||||
})
|
||||
spendableVtxos, err := a.getVtxos(ctx, withExpiryCoinselect, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, offchainAddr := range offchainAddrs {
|
||||
for _, v := range spendableVtxos {
|
||||
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if vtxoAddr == offchainAddr.Address {
|
||||
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||
Vtxo: v,
|
||||
Descriptor: offchainAddr.Descriptor,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||
vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect,
|
||||
)
|
||||
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var selectedBoardingCoins []types.Utxo
|
||||
var selectedCoins []client.DescriptorVtxo
|
||||
var changeAmount uint64
|
||||
|
||||
// if no receivers, self send all selected coins
|
||||
if len(outputs) <= 0 {
|
||||
selectedBoardingCoins = boardingUtxos
|
||||
selectedCoins = vtxos
|
||||
|
||||
amount := uint64(0)
|
||||
for _, utxo := range boardingUtxos {
|
||||
amount += utxo.Amount
|
||||
}
|
||||
for _, utxo := range vtxos {
|
||||
amount += utxo.Amount
|
||||
}
|
||||
|
||||
outputs = append(outputs, client.Output{
|
||||
Address: offchainAddrs[0].Address,
|
||||
Amount: amount,
|
||||
})
|
||||
|
||||
changeAmount = 0
|
||||
} else {
|
||||
selectedBoardingCoins, selectedCoins, changeAmount, err = utils.CoinSelect(
|
||||
boardingUtxos, vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if changeAmount > 0 {
|
||||
offchainAddr, _, err := a.wallet.NewAddress(ctx, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
changeReceiver := client.Output{
|
||||
outputs = append(outputs, client.Output{
|
||||
Address: offchainAddr.Address,
|
||||
Amount: changeAmount,
|
||||
}
|
||||
receiversOutput = append(receiversOutput, changeReceiver)
|
||||
})
|
||||
}
|
||||
|
||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||
inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingCoins))
|
||||
|
||||
for _, coin := range selectedCoins {
|
||||
inputs = append(inputs, client.Input{
|
||||
Outpoint: client.Outpoint{
|
||||
@@ -993,16 +1003,23 @@ func (a *covenantArkClient) sendOffchain(
|
||||
Descriptor: coin.Descriptor,
|
||||
})
|
||||
}
|
||||
for _, coin := range selectedBoardingCoins {
|
||||
inputs = append(inputs, client.Input{
|
||||
Outpoint: client.Outpoint{
|
||||
Txid: coin.Txid,
|
||||
VOut: coin.VOut,
|
||||
},
|
||||
Descriptor: coin.Descriptor,
|
||||
})
|
||||
}
|
||||
|
||||
paymentID, err := a.client.RegisterInputsForNextRound(
|
||||
ctx, inputs, "", // ephemeralPublicKey is not required for covenant
|
||||
)
|
||||
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := a.client.RegisterOutputsForNextRound(
|
||||
ctx, paymentID, receiversOutput,
|
||||
ctx, paymentID, outputs,
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1010,7 +1027,7 @@ func (a *covenantArkClient) sendOffchain(
|
||||
log.Infof("payment registered with id: %s", paymentID)
|
||||
|
||||
poolTxID, err := a.handleRoundStream(
|
||||
ctx, paymentID, selectedCoins, nil, "", receiversOutput,
|
||||
ctx, paymentID, selectedCoins, boardingUtxos, outputs,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -1023,7 +1040,7 @@ func (a *covenantArkClient) sendOffchain(
|
||||
func (a *covenantArkClient) addInputs(
|
||||
ctx context.Context,
|
||||
updater *psetv2.Updater,
|
||||
utxos []explorer.Utxo,
|
||||
utxos []types.Utxo,
|
||||
) error {
|
||||
// TODO works only with single-key wallet
|
||||
offchain, _, err := a.wallet.NewAddress(ctx, false)
|
||||
@@ -1055,7 +1072,7 @@ func (a *covenantArkClient) addInputs(
|
||||
if err := updater.AddInputs([]psetv2.InputArgs{
|
||||
{
|
||||
Txid: utxo.Txid,
|
||||
TxIndex: utxo.Vout,
|
||||
TxIndex: utxo.VOut,
|
||||
Sequence: sequence,
|
||||
},
|
||||
}); err != nil {
|
||||
@@ -1113,8 +1130,7 @@ func (a *covenantArkClient) handleRoundStream(
|
||||
ctx context.Context,
|
||||
paymentID string,
|
||||
vtxosToSign []client.DescriptorVtxo,
|
||||
boardingUtxos []explorer.Utxo,
|
||||
boardingDescriptor string,
|
||||
boardingUtxos []types.Utxo,
|
||||
receivers []client.Output,
|
||||
) (string, error) {
|
||||
eventsCh, close, err := a.client.GetEventStream(ctx, paymentID)
|
||||
@@ -1151,7 +1167,7 @@ func (a *covenantArkClient) handleRoundStream(
|
||||
log.Info("a round finalization started")
|
||||
|
||||
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
||||
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers,
|
||||
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, receivers,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -1178,8 +1194,7 @@ func (a *covenantArkClient) handleRoundFinalization(
|
||||
ctx context.Context,
|
||||
event client.RoundFinalizationEvent,
|
||||
vtxos []client.DescriptorVtxo,
|
||||
boardingUtxos []explorer.Utxo,
|
||||
boardingDescriptor string,
|
||||
boardingUtxos []types.Utxo,
|
||||
receivers []client.Output,
|
||||
) (signedForfeits []string, signedRoundTx string, err error) {
|
||||
if err = a.validateCongestionTree(event, receivers); err != nil {
|
||||
@@ -1193,13 +1208,23 @@ func (a *covenantArkClient) handleRoundFinalization(
|
||||
}
|
||||
}
|
||||
|
||||
if len(boardingUtxos) > 0 {
|
||||
boardingVtxoScript, err := tree.ParseVtxoScript(boardingDescriptor)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// if no boarding utxos inputs, we don't need to sign the round transaction
|
||||
if len(boardingUtxos) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
roundPtx, err := psetv2.NewPsetFromBase64(event.Tx)
|
||||
roundPtx, err := psetv2.NewPsetFromBase64(event.Tx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
updater, err := psetv2.NewUpdater(roundPtx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
for _, boardingUtxo := range boardingUtxos {
|
||||
boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Descriptor)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -1213,7 +1238,7 @@ func (a *covenantArkClient) handleRoundFinalization(
|
||||
AspPubkey: a.AspPubkey,
|
||||
}
|
||||
default:
|
||||
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor)
|
||||
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor)
|
||||
}
|
||||
|
||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||
@@ -1241,31 +1266,25 @@ func (a *covenantArkClient) handleRoundFinalization(
|
||||
ControlBlock: *ctrlBlock,
|
||||
}
|
||||
|
||||
updater, err := psetv2.NewUpdater(roundPtx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
for i, input := range updater.Pset.Inputs {
|
||||
for _, boardingUtxo := range boardingUtxos {
|
||||
if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.Vout == input.PreviousTxIndex {
|
||||
if err := updater.AddInTapLeafScript(i, tapscript); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
break
|
||||
if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.VOut == input.PreviousTxIndex {
|
||||
if err := updater.AddInTapLeafScript(i, tapscript); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b64, err := updater.Pset.ToBase64()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
b64, err := updater.Pset.ToBase64()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return signedForfeits, signedRoundTx, nil
|
||||
@@ -1518,8 +1537,8 @@ func (a *covenantArkClient) createAndSignForfeits(
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) coinSelectOnchain(
|
||||
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
||||
) ([]explorer.Utxo, uint64, error) {
|
||||
ctx context.Context, targetAmount uint64, exclude []types.Utxo,
|
||||
) ([]types.Utxo, uint64, error) {
|
||||
_, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -1527,7 +1546,7 @@ func (a *covenantArkClient) coinSelectOnchain(
|
||||
|
||||
now := time.Now()
|
||||
|
||||
fetchedUtxos := make([]explorer.Utxo, 0)
|
||||
fetchedUtxos := make([]types.Utxo, 0)
|
||||
for _, addr := range boardingAddrs {
|
||||
boardingDescriptor := addr.Descriptor
|
||||
|
||||
@@ -1550,14 +1569,14 @@ func (a *covenantArkClient) coinSelectOnchain(
|
||||
}
|
||||
|
||||
for _, utxo := range utxos {
|
||||
u := utxo.ToUtxo(boardingTimeout)
|
||||
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
|
||||
if u.SpendableAt.Before(now) {
|
||||
fetchedUtxos = append(fetchedUtxos, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selected := make([]explorer.Utxo, 0)
|
||||
selected := make([]types.Utxo, 0)
|
||||
selectedAmount := uint64(0)
|
||||
for _, utxo := range fetchedUtxos {
|
||||
if selectedAmount >= targetAmount {
|
||||
@@ -1565,7 +1584,7 @@ func (a *covenantArkClient) coinSelectOnchain(
|
||||
}
|
||||
|
||||
for _, excluded := range exclude {
|
||||
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
||||
if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1578,7 +1597,7 @@ func (a *covenantArkClient) coinSelectOnchain(
|
||||
return selected, selectedAmount - targetAmount, nil
|
||||
}
|
||||
|
||||
fetchedUtxos = make([]explorer.Utxo, 0)
|
||||
fetchedUtxos = make([]types.Utxo, 0)
|
||||
for _, addr := range redemptionAddrs {
|
||||
utxos, err := a.explorer.GetUtxos(addr.Address)
|
||||
if err != nil {
|
||||
@@ -1586,7 +1605,7 @@ func (a *covenantArkClient) coinSelectOnchain(
|
||||
}
|
||||
|
||||
for _, utxo := range utxos {
|
||||
u := utxo.ToUtxo(uint(a.UnilateralExitDelay))
|
||||
u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor)
|
||||
if u.SpendableAt.Before(now) {
|
||||
fetchedUtxos = append(fetchedUtxos, u)
|
||||
}
|
||||
@@ -1599,7 +1618,7 @@ func (a *covenantArkClient) coinSelectOnchain(
|
||||
}
|
||||
|
||||
for _, excluded := range exclude {
|
||||
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
||||
if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1648,11 +1667,11 @@ func (a *covenantArkClient) getRedeemBranches(
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) getOffchainBalance(
|
||||
ctx context.Context, addr string, computeVtxoExpiration bool,
|
||||
ctx context.Context, computeVtxoExpiration bool,
|
||||
) (uint64, map[int64]uint64, error) {
|
||||
amountByExpiration := make(map[int64]uint64, 0)
|
||||
|
||||
vtxos, err := a.getVtxos(ctx, addr, computeVtxoExpiration)
|
||||
vtxos, err := a.getVtxos(ctx, computeVtxoExpiration, nil)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
@@ -1675,18 +1694,24 @@ func (a *covenantArkClient) getOffchainBalance(
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) getVtxos(
|
||||
ctx context.Context, addr string, computeVtxoExpiration bool,
|
||||
ctx context.Context,
|
||||
withExpiryCoinselect bool, opts *CoinSelectOptions,
|
||||
) ([]client.Vtxo, error) {
|
||||
vtxos, _, err := a.client.ListVtxos(ctx, addr)
|
||||
spendableVtxos, _, err := a.ListVtxos(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !computeVtxoExpiration {
|
||||
return vtxos, nil
|
||||
if opts != nil && len(opts.OutpointsFilter) > 0 {
|
||||
spendableVtxos = filterByOutpoints(spendableVtxos, opts.OutpointsFilter)
|
||||
}
|
||||
|
||||
redeemBranches, err := a.getRedeemBranches(ctx, vtxos)
|
||||
if opts == nil || !opts.WithExpirySorting {
|
||||
return spendableVtxos, nil
|
||||
}
|
||||
|
||||
// if sorting by expiry is required, we need to get the expiration date of each vtxo
|
||||
redeemBranches, err := a.getRedeemBranches(ctx, spendableVtxos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1697,75 +1722,24 @@ func (a *covenantArkClient) getVtxos(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, vtxo := range vtxos {
|
||||
for i, vtxo := range spendableVtxos {
|
||||
if vtxo.Txid == vtxoTxid {
|
||||
vtxos[i].ExpiresAt = expiration
|
||||
spendableVtxos[i].ExpiresAt = expiration
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vtxos, nil
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) selfTransferAllPendingPayments(
|
||||
ctx context.Context, boardingUtxos []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
||||
) (string, error) {
|
||||
inputs := make([]client.Input, 0, len(boardingUtxos))
|
||||
|
||||
for _, utxo := range boardingUtxos {
|
||||
inputs = append(inputs, client.Input{
|
||||
Outpoint: client.Outpoint{
|
||||
Txid: utxo.Txid,
|
||||
VOut: utxo.Vout,
|
||||
},
|
||||
Descriptor: boardingDescriptor,
|
||||
})
|
||||
}
|
||||
|
||||
outputs := []client.Output{myself}
|
||||
|
||||
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := a.client.RegisterOutputsForNextRound(ctx, paymentID, outputs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
roundTxid, err := a.handleRoundStream(
|
||||
ctx, paymentID, make([]client.DescriptorVtxo, 0), boardingUtxos, boardingDescriptor, outputs,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return roundTxid, nil
|
||||
return spendableVtxos, nil
|
||||
}
|
||||
|
||||
func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []types.Transaction) {
|
||||
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
isPending := make(map[string]bool)
|
||||
for _, u := range utxos {
|
||||
isPending[u.Txid] = true
|
||||
}
|
||||
|
||||
allUtxos, err := a.getAllBoardingUtxos(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, u := range allUtxos {
|
||||
pending := false
|
||||
if isPending[u.Txid] {
|
||||
pending = true
|
||||
}
|
||||
|
||||
transactions = append(transactions, types.Transaction{
|
||||
TransactionKey: types.TransactionKey{
|
||||
BoardingTxid: u.Txid,
|
||||
@@ -1773,7 +1747,6 @@ func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []
|
||||
Amount: u.Amount,
|
||||
Type: types.TxReceived,
|
||||
CreatedAt: u.CreatedAt,
|
||||
IsPending: pending,
|
||||
})
|
||||
}
|
||||
return
|
||||
@@ -1796,9 +1769,7 @@ func vtxosToTxsCovenant(
|
||||
for _, v := range append(spendable, spent...) {
|
||||
// get vtxo amount
|
||||
amount := int(v.Amount)
|
||||
if !v.Pending {
|
||||
continue
|
||||
}
|
||||
|
||||
// find other spent vtxos that spent this one
|
||||
relatedVtxos := findVtxosBySpentBy(spent, v.Txid)
|
||||
for _, r := range relatedVtxos {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,8 +55,8 @@ func main() {
|
||||
onboardAmount := uint64(1_0000_0000) // 1 BTC
|
||||
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
|
||||
|
||||
log.Infof("alice claiming onboarding funds...")
|
||||
txid, err := aliceArkClient.Claim(ctx)
|
||||
log.Infof("alice settled the onboard funds...")
|
||||
txid, err := aliceArkClient.Settle(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ func main() {
|
||||
log.Infof("alice onchain balance: %d", aliceBalance.OnchainBalance.SpendableAmount)
|
||||
log.Infof("alice offchain balance: %d", aliceBalance.OffchainBalance.Total)
|
||||
|
||||
log.Infof("alice claiming onboarding funds...")
|
||||
txid, err := aliceArkClient.Claim(ctx)
|
||||
log.Infof("alice is settling the onboard funds...")
|
||||
txid, err := aliceArkClient.Settle(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Infof("alice claimed onboarding funds in round %s", txid)
|
||||
log.Infof("alice settled the onboard funds in round %s", txid)
|
||||
|
||||
fmt.Println("")
|
||||
log.Info("bob is setting up his ark wallet...")
|
||||
@@ -128,7 +128,7 @@ func main() {
|
||||
fmt.Println("")
|
||||
log.Infof("alice is sending %d sats to bob offchain...", amount)
|
||||
|
||||
if _, err = aliceArkClient.SendAsync(ctx, false, receivers); err != nil {
|
||||
if _, err = aliceArkClient.SendOffChain(ctx, false, receivers); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -158,13 +158,13 @@ func main() {
|
||||
log.Infof("bob offchain balance: %d", bobBalance.OffchainBalance.Total)
|
||||
|
||||
fmt.Println("")
|
||||
log.Info("bob is claiming the incoming payment...")
|
||||
roundTxid, err := bobArkClient.Claim(ctx)
|
||||
log.Info("bob is settling the received funds...")
|
||||
roundTxid, err := bobArkClient.Settle(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Infof("bob claimed the incoming payment in round %s", roundTxid)
|
||||
log.Infof("bob settled the received funds in round %s", roundTxid)
|
||||
|
||||
time.Sleep(500 * time.Second)
|
||||
}
|
||||
|
||||
@@ -90,19 +90,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function claimVtxos() {
|
||||
async function settleVtxos() {
|
||||
const password = document.getElementById("c_password").value;
|
||||
if (!password) {
|
||||
logMessage("Claim error: password is required");
|
||||
logMessage("Settle error: password is required");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await unlock(password);
|
||||
const txID = await claim();
|
||||
logMessage("Claimed money with tx ID: " + txID);
|
||||
const txID = await settle();
|
||||
logMessage("Settled money with tx ID: " + txID);
|
||||
} catch (err) {
|
||||
logMessage("Claim error: " + err.message);
|
||||
logMessage("Settle error: " + err.message);
|
||||
} finally {
|
||||
await lock(password);
|
||||
}
|
||||
@@ -166,7 +166,7 @@
|
||||
<input type="password" id="s_password" placeholder="password">
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="claimVtxos()">Claim</button>
|
||||
<button onclick="settleVtxos()">Settle</button>
|
||||
<input type="password" id="c_password" placeholder="password">
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/types"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/vulpemventures/go-elements/psetv2"
|
||||
@@ -24,39 +25,6 @@ const (
|
||||
LiquidExplorer = "liquid"
|
||||
)
|
||||
|
||||
type Utxo struct {
|
||||
Txid string
|
||||
Vout uint32
|
||||
Amount uint64
|
||||
Asset string // liquid only
|
||||
Delay uint
|
||||
SpendableAt time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (u *Utxo) Sequence() (uint32, error) {
|
||||
return common.BIP68Sequence(u.Delay)
|
||||
}
|
||||
|
||||
func newUtxo(explorerUtxo ExplorerUtxo, delay uint) Utxo {
|
||||
utxoTime := explorerUtxo.Status.Blocktime
|
||||
createdAt := time.Unix(utxoTime, 0)
|
||||
if utxoTime == 0 {
|
||||
createdAt = time.Time{}
|
||||
utxoTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return Utxo{
|
||||
Txid: explorerUtxo.Txid,
|
||||
Vout: explorerUtxo.Vout,
|
||||
Amount: explorerUtxo.Amount,
|
||||
Asset: explorerUtxo.Asset,
|
||||
Delay: delay,
|
||||
SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second),
|
||||
CreatedAt: createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
type ExplorerTx struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout []struct {
|
||||
@@ -85,8 +53,8 @@ type SpentStatus struct {
|
||||
SpentBy string `json:"txid,omitempty"`
|
||||
}
|
||||
|
||||
func (e ExplorerUtxo) ToUtxo(delay uint) Utxo {
|
||||
return newUtxo(e, delay)
|
||||
func (e ExplorerUtxo) ToUtxo(delay uint, descriptor string) types.Utxo {
|
||||
return newUtxo(e, delay, descriptor)
|
||||
}
|
||||
|
||||
type Explorer interface {
|
||||
@@ -446,3 +414,23 @@ func parseBitcoinTx(txStr string) (string, string, error) {
|
||||
|
||||
return txhex, txid, nil
|
||||
}
|
||||
|
||||
func newUtxo(explorerUtxo ExplorerUtxo, delay uint, descriptor string) types.Utxo {
|
||||
utxoTime := explorerUtxo.Status.Blocktime
|
||||
createdAt := time.Unix(utxoTime, 0)
|
||||
if utxoTime == 0 {
|
||||
createdAt = time.Time{}
|
||||
utxoTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return types.Utxo{
|
||||
Txid: explorerUtxo.Txid,
|
||||
VOut: explorerUtxo.Vout,
|
||||
Amount: explorerUtxo.Amount,
|
||||
Asset: explorerUtxo.Asset,
|
||||
Delay: delay,
|
||||
SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second),
|
||||
CreatedAt: createdAt,
|
||||
Descriptor: descriptor,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/types"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
@@ -23,10 +24,14 @@ import (
|
||||
)
|
||||
|
||||
func CoinSelect(
|
||||
vtxos []client.DescriptorVtxo, amount, dust uint64, sortByExpirationTime bool,
|
||||
) ([]client.DescriptorVtxo, uint64, error) {
|
||||
selected := make([]client.DescriptorVtxo, 0)
|
||||
notSelected := make([]client.DescriptorVtxo, 0)
|
||||
boardingUtxos []types.Utxo,
|
||||
vtxos []client.DescriptorVtxo,
|
||||
amount,
|
||||
dust uint64,
|
||||
sortByExpirationTime bool,
|
||||
) ([]types.Utxo, []client.DescriptorVtxo, uint64, error) {
|
||||
selected, notSelected := make([]client.DescriptorVtxo, 0), make([]client.DescriptorVtxo, 0)
|
||||
selectedBoarding, notSelectedBoarding := make([]types.Utxo, 0), make([]types.Utxo, 0)
|
||||
selectedAmount := uint64(0)
|
||||
|
||||
if sortByExpirationTime {
|
||||
@@ -38,6 +43,20 @@ func CoinSelect(
|
||||
|
||||
return vtxos[i].ExpiresAt.Before(*vtxos[j].ExpiresAt)
|
||||
})
|
||||
|
||||
sort.SliceStable(boardingUtxos, func(i, j int) bool {
|
||||
return boardingUtxos[i].SpendableAt.Before(boardingUtxos[j].SpendableAt)
|
||||
})
|
||||
}
|
||||
|
||||
for _, boardingUtxo := range boardingUtxos {
|
||||
if selectedAmount >= amount {
|
||||
notSelectedBoarding = append(notSelectedBoarding, boardingUtxo)
|
||||
break
|
||||
}
|
||||
|
||||
selectedBoarding = append(selectedBoarding, boardingUtxo)
|
||||
selectedAmount += boardingUtxo.Amount
|
||||
}
|
||||
|
||||
for _, vtxo := range vtxos {
|
||||
@@ -51,7 +70,7 @@ func CoinSelect(
|
||||
}
|
||||
|
||||
if selectedAmount < amount {
|
||||
return nil, 0, fmt.Errorf("not enough funds to cover amount %d", amount)
|
||||
return nil, nil, 0, fmt.Errorf("not enough funds to cover amount %d", amount)
|
||||
}
|
||||
|
||||
change := selectedAmount - amount
|
||||
@@ -60,10 +79,13 @@ func CoinSelect(
|
||||
if len(notSelected) > 0 {
|
||||
selected = append(selected, notSelected[0])
|
||||
change += notSelected[0].Amount
|
||||
} else if len(notSelectedBoarding) > 0 {
|
||||
selectedBoarding = append(selectedBoarding, notSelectedBoarding[0])
|
||||
change += notSelectedBoarding[0].Amount
|
||||
}
|
||||
}
|
||||
|
||||
return selected, change, nil
|
||||
return selectedBoarding, selected, change, nil
|
||||
}
|
||||
|
||||
func ParseLiquidAddress(addr string) (
|
||||
|
||||
@@ -3,6 +3,7 @@ package arksdk
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||
grpcclient "github.com/ark-network/ark/pkg/client-sdk/client/grpc"
|
||||
restclient "github.com/ark-network/ark/pkg/client-sdk/client/rest"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||
@@ -123,3 +124,10 @@ type balanceRes struct {
|
||||
offchainBalanceByExpiration map[int64]uint64
|
||||
err error
|
||||
}
|
||||
|
||||
type CoinSelectOptions struct {
|
||||
// If true, coin selector will select coins closest to expiry first.
|
||||
WithExpirySorting bool
|
||||
// If specified, coin selector will select only coins in the list.
|
||||
OutpointsFilter []client.Outpoint
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ type Transaction struct {
|
||||
TransactionKey
|
||||
Amount uint64
|
||||
Type TxType
|
||||
IsPending bool
|
||||
Settled bool
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
@@ -103,3 +103,19 @@ type TransactionEvent struct {
|
||||
Tx Transaction
|
||||
Event EventType
|
||||
}
|
||||
|
||||
type Utxo struct {
|
||||
Txid string
|
||||
VOut uint32
|
||||
Amount uint64
|
||||
Asset string // liquid only
|
||||
Delay uint
|
||||
SpendableAt time.Time
|
||||
CreatedAt time.Time
|
||||
Descriptor string
|
||||
Spent bool
|
||||
}
|
||||
|
||||
func (u *Utxo) Sequence() (uint32, error) {
|
||||
return common.BIP68Sequence(u.Delay)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func init() {
|
||||
js.Global().Set("sendOnChain", SendOnChainWrapper())
|
||||
js.Global().Set("sendOffChain", SendOffChainWrapper())
|
||||
js.Global().Set("sendAsync", SendAsyncWrapper())
|
||||
js.Global().Set("claim", ClaimWrapper())
|
||||
js.Global().Set("settle", SettleWrapper())
|
||||
js.Global().Set("unilateralRedeem", UnilateralRedeemWrapper())
|
||||
js.Global().Set("collaborativeRedeem", CollaborativeRedeemWrapper())
|
||||
js.Global().Set("getTransactionHistory", GetTransactionHistoryWrapper())
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
arksdk "github.com/ark-network/ark/pkg/client-sdk"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||
"github.com/ark-network/ark/pkg/client-sdk/wallet"
|
||||
singlekeywallet "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey"
|
||||
)
|
||||
@@ -186,17 +187,17 @@ func SendOnChainWrapper() js.Func {
|
||||
if len(args) != 1 {
|
||||
return nil, errors.New("invalid number of args")
|
||||
}
|
||||
receivers := make([]arksdk.Receiver, args[0].Length())
|
||||
for i := 0; i < args[0].Length(); i++ {
|
||||
receiver := args[0].Index(i)
|
||||
receivers[i] = arksdk.NewBitcoinReceiver(
|
||||
receiver.Get("To").String(), uint64(receiver.Get("Amount").Int()),
|
||||
)
|
||||
|
||||
receivers, err := parseReceivers(args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txID, err := arkSdkClient.SendOnChain(
|
||||
context.Background(), receivers,
|
||||
)
|
||||
if receivers == nil || len(receivers) == 0 {
|
||||
return nil, errors.New("no receivers specified")
|
||||
}
|
||||
|
||||
txID, err := arkSdkClient.SendOnChain(context.Background(), receivers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,13 +210,11 @@ func SendOffChainWrapper() js.Func {
|
||||
if len(args) != 2 {
|
||||
return nil, errors.New("invalid number of args")
|
||||
}
|
||||
|
||||
withExpiryCoinselect := args[0].Bool()
|
||||
receivers := make([]arksdk.Receiver, args[1].Length())
|
||||
for i := 0; i < args[1].Length(); i++ {
|
||||
receiver := args[1].Index(i)
|
||||
receivers[i] = arksdk.NewBitcoinReceiver(
|
||||
receiver.Get("To").String(), uint64(receiver.Get("Amount").Int()),
|
||||
)
|
||||
receivers, err := parseReceivers(args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txID, err := arkSdkClient.SendOffChain(
|
||||
@@ -233,13 +232,15 @@ func SendAsyncWrapper() js.Func {
|
||||
if len(args) != 2 {
|
||||
return nil, errors.New("invalid number of args")
|
||||
}
|
||||
|
||||
withExpiryCoinselect := args[0].Bool()
|
||||
receivers := make([]arksdk.Receiver, args[1].Length())
|
||||
for i := 0; i < args[1].Length(); i++ {
|
||||
receiver := args[1].Index(i)
|
||||
receivers[i] = arksdk.NewBitcoinReceiver(
|
||||
receiver.Get("To").String(), uint64(receiver.Get("Amount").Int()),
|
||||
)
|
||||
receivers, err := parseReceivers(args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if receivers == nil || len(receivers) == 0 {
|
||||
return nil, errors.New("no receivers specified")
|
||||
}
|
||||
|
||||
txID, err := arkSdkClient.SendAsync(
|
||||
@@ -252,13 +253,13 @@ func SendAsyncWrapper() js.Func {
|
||||
})
|
||||
}
|
||||
|
||||
func ClaimWrapper() js.Func {
|
||||
func SettleWrapper() js.Func {
|
||||
return JSPromise(func(args []js.Value) (interface{}, error) {
|
||||
if len(args) != 0 {
|
||||
return nil, errors.New("invalid number of args")
|
||||
}
|
||||
|
||||
resp, err := arkSdkClient.Claim(context.Background())
|
||||
resp, err := arkSdkClient.Settle(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -306,7 +307,7 @@ func GetTransactionHistoryWrapper() js.Func {
|
||||
"redeemTxid": record.RedeemTxid,
|
||||
"amount": strconv.Itoa(int(record.Amount)),
|
||||
"type": record.Type,
|
||||
"isPending": record.IsPending,
|
||||
"settled": record.Settled,
|
||||
"createdAt": record.CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
@@ -433,3 +434,63 @@ func JSPromise(fn promise) js.Func {
|
||||
return promiseConstructor.New(handler)
|
||||
})
|
||||
}
|
||||
|
||||
func parseReceivers(jsReceivers js.Value) ([]arksdk.Receiver, error) {
|
||||
if jsReceivers.IsNull() || jsReceivers.IsUndefined() {
|
||||
return nil, nil // Return nil slice if input is null or undefined
|
||||
}
|
||||
|
||||
if jsReceivers.Type() != js.TypeObject || jsReceivers.Get("length").Type() != js.TypeNumber {
|
||||
return nil, errors.New("invalid receivers argument: expected array")
|
||||
}
|
||||
|
||||
length := jsReceivers.Length()
|
||||
if length == 0 {
|
||||
return []arksdk.Receiver{}, nil // Return empty slice if input array is empty
|
||||
}
|
||||
|
||||
receivers := make([]arksdk.Receiver, length)
|
||||
for i := 0; i < length; i++ {
|
||||
receiver := jsReceivers.Index(i)
|
||||
if receiver.Type() != js.TypeObject {
|
||||
return nil, fmt.Errorf("invalid receiver at index %d: expected object", i)
|
||||
}
|
||||
|
||||
to := receiver.Get("To")
|
||||
amount := receiver.Get("Amount")
|
||||
if to.Type() != js.TypeString || amount.Type() != js.TypeNumber {
|
||||
return nil, fmt.Errorf("invalid receiver at index %d: expected 'To' (string) and 'Amount' (number)", i)
|
||||
}
|
||||
|
||||
receivers[i] = arksdk.NewBitcoinReceiver(to.String(), uint64(amount.Int()))
|
||||
}
|
||||
|
||||
return receivers, nil
|
||||
}
|
||||
|
||||
func parseOutpoints(jsOutpoints js.Value) ([]client.Outpoint, error) {
|
||||
if jsOutpoints.Length() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
outpoints := make([]client.Outpoint, jsOutpoints.Length())
|
||||
for i := 0; i < jsOutpoints.Length(); i++ {
|
||||
jsOutpoint := jsOutpoints.Index(i)
|
||||
if jsOutpoint.Type() != js.TypeObject {
|
||||
return nil, fmt.Errorf("invalid outpoint at index %d: expected object", i)
|
||||
}
|
||||
|
||||
txid := jsOutpoint.Get("Txid")
|
||||
vout := jsOutpoint.Get("Vout")
|
||||
if txid.Type() != js.TypeString || vout.Type() != js.TypeNumber {
|
||||
return nil, fmt.Errorf("invalid outpoint at index %d: expected 'Txid' (string) and 'Vout' (number)", i)
|
||||
}
|
||||
|
||||
outpoints[i] = client.Outpoint{
|
||||
Txid: txid.String(),
|
||||
VOut: uint32(vout.Int()),
|
||||
}
|
||||
}
|
||||
|
||||
return outpoints, nil
|
||||
}
|
||||
|
||||
@@ -39,5 +39,3 @@ func PrintBuildInfo() {
|
||||
func GetVersion() string {
|
||||
return Version
|
||||
}
|
||||
|
||||
// You can add more build-related functions here as needed
|
||||
|
||||
@@ -21,8 +21,3 @@ func main() {
|
||||
println("ARK SDK WebAssembly module initialized")
|
||||
<-c
|
||||
}
|
||||
|
||||
func init() {
|
||||
// You can add any additional initialization here if needed
|
||||
// This runs before the main function
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user